diff --git a/Fab2ApprovalSystem/App_Start/WebApiConfig.cs b/Fab2ApprovalSystem/App_Start/WebApiConfig.cs index 105d51b..1343595 100644 --- a/Fab2ApprovalSystem/App_Start/WebApiConfig.cs +++ b/Fab2ApprovalSystem/App_Start/WebApiConfig.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; +using System.Web.Http; -namespace Fab2ApprovalSystem -{ +namespace Fab2ApprovalSystem { public static class WebApiConfig { public static void Register(HttpConfiguration config) diff --git a/Fab2ApprovalSystem/Controllers/AccountController.cs b/Fab2ApprovalSystem/Controllers/AccountController.cs index 6ca9721..c9d22ac 100644 --- a/Fab2ApprovalSystem/Controllers/AccountController.cs +++ b/Fab2ApprovalSystem/Controllers/AccountController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using System.Web; @@ -15,8 +14,8 @@ using Fab2ApprovalSystem.DMO; using Microsoft.AspNet.Identity.Owin; using System.Net.Http; using Newtonsoft.Json; -using System.Net.Http.Headers; using System.Text; +using System.Net; namespace Fab2ApprovalSystem.Controllers { [Authorize] @@ -129,6 +128,86 @@ namespace Fab2ApprovalSystem.Controllers { } + [HttpPost] + [AllowAnonymous] + public async Task ExternalAuthSetup(AuthAttempt authAttempt) { + try { + bool isLoginValid; + + HttpClient httpClient = HttpClientFactory.Create(); + httpClient.BaseAddress = new Uri(_apiBaseUrl); + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "auth/refresh"); + + request.Content = new StringContent(JsonConvert.SerializeObject(authAttempt), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request); + + if (!httpResponseMessage.IsSuccessStatusCode) + throw new Exception($"The authentication API failed, because {httpResponseMessage.ReasonPhrase}"); + + string responseContent = await httpResponseMessage.Content.ReadAsStringAsync(); + + LoginResult loginResult = JsonConvert.DeserializeObject(responseContent); + +#if(DEBUG) + isLoginValid = true; + +#endif +#if (!DEBUG) + + bool isIFX = false; + //domainProvider = Membership.Providers["NA_ADMembershipProvider"]; + //isLoginValid = domainProvider.ValidateUser(model.LoginID, model.Password); + + if (GlobalVars.DBConnection.ToUpper() == "TEST" || GlobalVars.DBConnection.ToUpper() == "QUALITY") { + isLoginValid = true; + } else { + isLoginValid = loginResult.IsAuthenticated; + if (isLoginValid) isIFX = true; + + } + +#endif + + if (isLoginValid) { + UserAccountDMO userDMO = new UserAccountDMO(); + LoginModel user = userDMO.GetUser(authAttempt.LoginID); + if (user != null) { + Session["JWT"] = loginResult.AuthTokens.JwtToken; + Session["RefreshToken"] = loginResult.AuthTokens.RefreshToken; + + Session[GlobalVars.SESSION_USERID] = user.UserID; + Session[GlobalVars.SESSION_USERNAME] = user.FullName; + Session[GlobalVars.IS_ADMIN] = user.IsAdmin; + Session[GlobalVars.IS_MANAGER] = user.IsManager; + Session[GlobalVars.OOO] = user.OOO; + Session[GlobalVars.CAN_CREATE_PARTS_REQUEST] = user.IsAdmin || PartsRequestController.CanCreatePartsRequest(user.UserID); + + FormsAuthentication.SetAuthCookie(user.LoginID, true); + + return new HttpResponseMessage(HttpStatusCode.OK); + } else { + ModelState.AddModelError("", "The user name does not exist in the DB. Please contact the System Admin"); + + return new HttpResponseMessage(HttpStatusCode.NotFound); + } + } else { + ModelState.AddModelError("", "The user name or password provided is incorrect."); + + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + } + } catch (Exception ex) { + Functions.WriteEvent(@User.Identity.Name + " " + ex.InnerException, System.Diagnostics.EventLogEntryType.Error); + EventLogDMO.Add(new WinEventLog() { IssueID = 99999, UserID = @User.Identity.Name, DocumentType = "Login", OperationType = "Error", Comments = "Reject - " + ex.Message }); + ModelState.AddModelError("", ex.Message); + + return new HttpResponseMessage(HttpStatusCode.InternalServerError); + } + } + // GET: /Account/Register [AllowAnonymous] public ActionResult Register() { diff --git a/Fab2ApprovalSystem/Controllers/MRBController.cs b/Fab2ApprovalSystem/Controllers/MRBController.cs index 4c00277..b0a3719 100644 --- a/Fab2ApprovalSystem/Controllers/MRBController.cs +++ b/Fab2ApprovalSystem/Controllers/MRBController.cs @@ -102,53 +102,15 @@ namespace Fab2ApprovalSystem.Controllers // GET: /MRB/Edit/5 public ActionResult Edit(int issueID) { - MRB mrb = new MRB(); - int isITARCompliant = 1; - ViewBag.Status = "Pending"; - ViewBag.IsApprover = "false"; - ViewBag.IsCloser = "false"; + string jwt = Session["JWT"].ToString(); + string encodedJwt = System.Net.WebUtility.UrlEncode(jwt); + string refreshToken = Session["RefreshToken"].ToString(); + string encodedRefreshToken = System.Net.WebUtility.UrlEncode(refreshToken); + string wasmClientUrl = Environment.GetEnvironmentVariable("FabApprovalWasmClientUrl") ?? + "https://localhost:7255"; + string mrbUrl = $"{wasmClientUrl}/redirect?jwt={encodedJwt}&refreshToken={encodedRefreshToken}&redirectPath=/mrb/{issueID}"; - //ViewBag.IsApproved = "false"; - //ViewBag.IsClosed = "false"; - PopulateCloseToQDB(); - mrb = mrbDMO.GetMRBItem(issueID, out isITARCompliant, (int)Session[GlobalVars.SESSION_USERID]); - ViewBag.UserList = mrbDMO.GetUserList(); - - if (isITARCompliant == 0) // not ITAR Compliant - { - return View("UnAuthorizedAccess"); - } - else - { - if (mrb.ApprovalStatus == (int)GlobalVars.ApprovalOption.Approved) - { - //ViewBag.IsApproved = "true"; - ViewBag.Status = "Approved"; - } - else if (mrb.ApprovalStatus == (int)GlobalVars.ApprovalOption.Closed) - { - ViewBag.Status = "Closed"; - //ViewBag.IsClosed = "true"; - } - List userList = MiscDMO.GetApproversListByDocument(issueID, mrb.CurrentStep, (int)GlobalVars.DocumentType.MRB); - ApproversListViewModel appUser = userList.Find(delegate (ApproversListViewModel al) { return al.UserID == (int)Session[GlobalVars.SESSION_USERID]; }); - if (appUser != null) - { - ViewBag.IsApprover = "true"; - } - - - } - - // can edit - ViewBag.Owners = MiscDMO.GetUserList(); - ViewBag.Modules = mrbDMO.GetModules(); - //ViewBag.Dispositions = mrbDMO.GetDispositions(); - ViewBag.RiskAssessments = mrbDMO.GetRiskAssessments(); - ViewBag.PartGroups = mrbDMO.GetPartGroups(); - ViewBag.DispoTypes = mrbDMO.GetDispositions(issueID).Select(d => new { d.DispositionType }); - - return View(mrb); + return Redirect(mrbUrl); } // @@ -178,39 +140,15 @@ namespace Fab2ApprovalSystem.Controllers /// public ActionResult ReadOnly(int issueID) { - MRB mrb = new MRB(); - int isITARCompliant = 1; + string jwt = Session["JWT"].ToString(); + string encodedJwt = System.Net.WebUtility.UrlEncode(jwt); + string refreshToken = Session["RefreshToken"].ToString(); + string encodedRefreshToken = System.Net.WebUtility.UrlEncode(refreshToken); + string wasmClientUrl = Environment.GetEnvironmentVariable("FabApprovalWasmClientUrl") ?? + "https://localhost:7255"; + string mrbUrl = $"{wasmClientUrl}/redirect?jwt={encodedJwt}&refreshToken={encodedRefreshToken}&redirectPath=/mrb/{issueID}"; - - try - { - if (isITARCompliant == 0) // not ITAR Compliant - { - return View("UnAuthorizedAccess"); - } - else - { - mrb = mrbDMO.GetMRBItem(issueID, out isITARCompliant, (int)Session[GlobalVars.SESSION_USERID]); - - ViewBag.Owners = MiscDMO.GetUserList(); - ViewBag.Modules = mrbDMO.GetModules(); - //ViewBag.Dispositions = mrbDMO.GetDispositions(); - ViewBag.RiskAssessments = mrbDMO.GetRiskAssessments(); - ViewBag.PartGroups = mrbDMO.GetPartGroups(); - ViewBag.DispoTypes = mrbDMO.GetDispositions(issueID).Select(d => new { d.DispositionType }); - } - - return View(mrb); - } - - catch (Exception e) - { - string exceptionString = e.Message.ToString().Trim().Length > 500 ? "IssueID=" + issueID.ToString() + " " + e.Message.ToString().Substring(0, 250) : e.Message.ToString(); - Functions.WriteEvent(@User.Identity.Name + "\r\n ReadOnly Disposition\r\n" + e.Message.ToString(), System.Diagnostics.EventLogEntryType.Error); - EventLogDMO.Add(new WinEventLog() { IssueID = issueID, UserID = @User.Identity.Name, DocumentType = "Lot Disposition", OperationType = "Error", Comments = exceptionString }); - throw new Exception(e.Message); - - } + return Redirect(mrbUrl); } // diff --git a/Fab2ApprovalSystem/ViewModels/OpenActionItemViewModel.cs b/Fab2ApprovalSystem/ViewModels/OpenActionItemViewModel.cs index bf56b65..b1722fe 100644 --- a/Fab2ApprovalSystem/ViewModels/OpenActionItemViewModel.cs +++ b/Fab2ApprovalSystem/ViewModels/OpenActionItemViewModel.cs @@ -14,6 +14,6 @@ namespace Fab2ApprovalSystem.ViewModels public string Originator { get; set; } public DateTime? AssignedDate { get; set; } public DateTime? DueDate { get; set; } - public string pcrMesaID { get; set; } + public string pcrMesaID { get; set; } = string.Empty; } } diff --git a/Fab2ApprovalSystem/Views/ECN/Acknowledge.cshtml b/Fab2ApprovalSystem/Views/ECN/Acknowledge.cshtml index 588791d..1f6afa6 100644 --- a/Fab2ApprovalSystem/Views/ECN/Acknowledge.cshtml +++ b/Fab2ApprovalSystem/Views/ECN/Acknowledge.cshtml @@ -227,7 +227,7 @@ )
- ECN: Qualtiy and Dept. Specific
MRB: Quality, Production, Engineering, OPC
PCRB: Quality, Production, Engineering, Dept. Specific (scope of change) + ECN: Qualtiy and Dept. Specific
PCRB: Quality, Production, Engineering, Dept. Specific (scope of change)
diff --git a/Fab2ApprovalSystem/Views/ECN/Edit.cshtml b/Fab2ApprovalSystem/Views/ECN/Edit.cshtml index 2c53a89..eccb0e6 100644 --- a/Fab2ApprovalSystem/Views/ECN/Edit.cshtml +++ b/Fab2ApprovalSystem/Views/ECN/Edit.cshtml @@ -171,7 +171,7 @@ )
- ECN: Qualtiy and Dept. Specific
MRB: Quality, Production, Engineering, OPC
PCRB: Quality, Production, Engineering, Dept. Specific (scope of change) + ECN: Qualtiy and Dept. Specific
PCRB: Quality, Production, Engineering, Dept. Specific (scope of change)
diff --git a/Fab2ApprovalSystem/Views/ECN/EditApproval.cshtml b/Fab2ApprovalSystem/Views/ECN/EditApproval.cshtml index 4caf838..4d0943e 100644 --- a/Fab2ApprovalSystem/Views/ECN/EditApproval.cshtml +++ b/Fab2ApprovalSystem/Views/ECN/EditApproval.cshtml @@ -259,7 +259,7 @@ )
- ECN: Qualtiy and Dept. Specific
MRB: Quality, Production, Engineering, OPC
PCRB: Quality, Production, Engineering, Dept. Specific (scope of change) + ECN: Qualtiy and Dept. Specific
PCRB: Quality, Production, Engineering, Dept. Specific (scope of change)
diff --git a/Fab2ApprovalSystem/Views/Home/_HomeLayout.cshtml b/Fab2ApprovalSystem/Views/Home/_HomeLayout.cshtml index 4de7078..d4fe335 100644 --- a/Fab2ApprovalSystem/Views/Home/_HomeLayout.cshtml +++ b/Fab2ApprovalSystem/Views/Home/_HomeLayout.cshtml @@ -92,11 +92,15 @@ "https://localhost:7255"; string mrbUrl = wasmClientUrl + "/redirect?jwt=" + encodedJwt + "&refreshToken=" + encodedRefreshToken + "&redirectPath=/mrb/new";
  • Create MRB
  • + @*string pcrbUrl = wasmClientUrl + "/redirect?jwt=" + encodedJwt + "&refreshToken=" + encodedRefreshToken + "&redirectPath=/pcrb/new"; +
  • Create PCRB
  • *@ } else { string wasmClientUrl = Environment.GetEnvironmentVariable("FabApprovalWasmClientUrl") ?? "https://localhost:7255"; string mrbUrl = wasmClientUrl + "/redirect?redirectPath=/mrb/new";
  • Create MRB
  • + @*string pcrbUrl = wasmClientUrl + "/redirect?redirectPath=/pcrb/new"; +
  • Create PCRB
  • *@ } @*
  • Create Special Work Request
  • *@ @*
  • Create PCR
  • *@ @@ -148,6 +152,8 @@ "https://localhost:7255"; string mrbUrl = wasmClientUrl + "/redirect?jwt=" + encodedJwt + "&refreshToken=" + encodedRefreshToken + "&redirectPath=/mrb/all"; menu.Add().Text("MRB").Url(mrbUrl); + //string pcrbUrl = wasmClientUrl + "/redirect?jwt=" + encodedJwt + "&refreshToken=" + encodedRefreshToken + "&redirectPath=/pcrb/all"; + //menu.Add().Text("PCRB").Url(pcrbUrl); //menu.Add().Text("Special Work Requests").Action("SpecialWorkRequestList", "Home"); //menu.Add().Text("PCRB").Action("ChangeControlList", "Home"); //menu.Add().Text("MRB").Action("MRBList", "Home"); diff --git a/Fab2ApprovalSystem/Web.config b/Fab2ApprovalSystem/Web.config index 2eee23c..04cd73f 100644 --- a/Fab2ApprovalSystem/Web.config +++ b/Fab2ApprovalSystem/Web.config @@ -132,6 +132,11 @@ + + + + + diff --git a/MesaFabApproval.API/Controllers/MRBController.cs b/MesaFabApproval.API/Controllers/MRBController.cs index f8fb8cb..1de067f 100644 --- a/MesaFabApproval.API/Controllers/MRBController.cs +++ b/MesaFabApproval.API/Controllers/MRBController.cs @@ -15,10 +15,14 @@ public class MRBController : ControllerBase { private readonly IMRBService _mrbService; private readonly IMonInWorkerClient _monInClient; + private readonly string _mrbAttachmentPath; + public MRBController(ILogger logger, IMRBService mrbService, IMonInWorkerClient monInClient) { _logger = logger ?? throw new ArgumentNullException("ILogger not injected"); _mrbService = mrbService ?? throw new ArgumentNullException("IMRBService not injected"); _monInClient = monInClient ?? throw new ArgumentNullException("IMonInWorkerClient not injected"); + _mrbAttachmentPath = Environment.GetEnvironmentVariable("FabApprovalMrbAttachmentPath") ?? + throw new ArgumentNullException("FabApprovalMrbAttachmentPath environment variable not found"); } [HttpPost] @@ -692,6 +696,64 @@ public class MRBController : ControllerBase { } } + [AllowAnonymous] + [HttpGet] + [Route("mrb/actions/csvFile")] + public async Task GetMRBActionsCsvFile(int mrbNumber) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to get MRB actions CSC file"); + + if (!(await _mrbService.MRBNumberIsValid(mrbNumber))) + throw new ArgumentException($"{mrbNumber} is not a valid MRB#"); + + string path = $"{_mrbAttachmentPath}\\{mrbNumber}\\mrb{mrbNumber}Actions.csv"; + + await _mrbService.ConvertActionsToCsvFile(mrbNumber, path); + + byte[] fs = System.IO.File.ReadAllBytes(path); + + const string defaultContentType = "application/octet-stream"; + + FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider(); + + if (!contentTypeProvider.TryGetContentType(path, out string? contentType)) { + contentType = defaultContentType; + } + + return new FileContentResult(fs, contentType) { + FileDownloadName = $"mrb{mrbNumber}Actions.csv" + }; + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get MRB actions CSC file, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetMRBActionsCSVFile"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + [HttpDelete] [Route("mrb/attach")] public async Task DeleteMRBAttachment(MRBAttachment attachment) { diff --git a/MesaFabApproval.API/Controllers/PCRBController.cs b/MesaFabApproval.API/Controllers/PCRBController.cs index c5998fd..b24659c 100644 --- a/MesaFabApproval.API/Controllers/PCRBController.cs +++ b/MesaFabApproval.API/Controllers/PCRBController.cs @@ -4,6 +4,7 @@ using MesaFabApproval.Shared.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; namespace MesaFabApproval.API.Controllers; @@ -261,4 +262,865 @@ public class PCRBController : ControllerBase { } } } + + [HttpPost] + [Route("pcrb/attachment")] + public async Task UploadAttachment([FromForm] IFormFile file, [FromForm] PCRBAttachment attachment) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to upload PCRB attachment"); + + if (file is null) throw new ArgumentNullException("File cannot be null"); + if (file.Length <= 0) throw new ArgumentException("File size cannot be zero"); + if (attachment is null) throw new ArgumentNullException("Attachment cannot be null"); + + await _pcrbService.UploadAttachment(file, attachment); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot upload PCRB attachment, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "UploadPCRBAttachment"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpGet] + [Route("pcrb/attachments")] + public async Task GetAttachmentsByPlanNumber(int planNumber, bool bypassCache) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to get MRB attachments for MRB {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + List attachments = (await _pcrbService.GetAttachmentsByPlanNumber(planNumber, bypassCache)).ToList(); + + return Ok(attachments); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get attachments for PCRB Plan# {planNumber}, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetPCRBAttachments"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [AllowAnonymous] + [HttpGet] + [Route("pcrb/attachmentFile")] + public async Task GetMRBAttachmentFile(string path, string fileName) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to get PCRB attachment file"); + + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Path cannot be null or empty"); + if (!System.IO.File.Exists(path)) throw new ArgumentException("No file exists at provided path"); + if (string.IsNullOrWhiteSpace(fileName)) throw new ArgumentException("Filename cannot be null or empty"); + + byte[] fs = System.IO.File.ReadAllBytes(path); + + const string defaultContentType = "application/octet-stream"; + + FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider(); + + if (!contentTypeProvider.TryGetContentType(path, out string? contentType)) { + contentType = defaultContentType; + } + + return new FileContentResult(fs, contentType) { + FileDownloadName = fileName + }; + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get PCRB attachment file, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetPCRBAttachmentFile"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPut] + [Route("pcrb/attachment")] + public async Task UpdateAttachment(PCRBAttachment attachment) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to update an attachment"); + + if (attachment is null) throw new ArgumentNullException("attachment cannot be null"); + + await _pcrbService.UpdateAttachment(attachment); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot update attachment, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "UpdatePCRBAttachment"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpDelete] + [Route("pcrb/attachment")] + public async Task DeleteAttachment(PCRBAttachment attachment) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to delete an attachment"); + + if (attachment is null) throw new ArgumentNullException("attachment cannot be null"); + + await _pcrbService.DeleteAttachment(attachment); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot delete attachment, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "DeletePCRBAttachment"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/actionItem")] + public async Task CreateActionItem(PCRBActionItem actionItem) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to create PCRB action item"); + + if (actionItem is null) throw new ArgumentNullException("action item cannot be null"); + + await _pcrbService.CreateNewActionItem(actionItem); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot create PCRB action item, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "CreatePCRBActionItem"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPut] + [Route("pcrb/actionItem")] + public async Task UpdateActionItem(PCRBActionItem actionItem) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to update an action item"); + + if (actionItem is null) throw new ArgumentNullException("action item cannot be null"); + + await _pcrbService.UpdateActionItem(actionItem); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot update action item, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "UpdatePCRBActionItem"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpDelete] + [Route("pcrb/actionItem")] + public async Task DeleteActionItem(int id) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to delete an action item"); + + if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB action item ID"); + + await _pcrbService.DeleteActionItem(id); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot delete action item, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "DeletePCRBActionItem"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpGet] + [Route("pcrb/actionItems")] + public async Task GetActionItemsByPlanNumber(int planNumber, bool bypassCache) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to get PCRB action items for plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + List actionItems = (await _pcrbService.GetActionItemsForPlanNumber(planNumber, bypassCache)).ToList(); + + return Ok(actionItems); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get action items for plan# {planNumber}, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetPCRBActionItems"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/pcr3Document")] + public async Task CreatePCR3Document(PCR3Document document) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to create PCR3 document"); + + if (document is null) throw new ArgumentNullException("document cannot be null"); + + await _pcrbService.CreatePCR3Document(document); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot create PCR3 document, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "CreatePCR3Document"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPut] + [Route("pcrb/pcr3Document")] + public async Task UpdatePCR3Document(PCR3Document document) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to update a PCR3 document"); + + if (document is null) throw new ArgumentNullException("document cannot be null"); + + await _pcrbService.UpdatePCR3Document(document); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot update PCR3 document, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "UpdatePCR3Document"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpGet] + [Route("pcrb/pcr3Documents")] + public async Task GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to get PCR3 documents for plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + List documents = (await _pcrbService.GetPCR3DocumentsForPlanNumber(planNumber, bypassCache)).ToList(); + + return Ok(documents); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get PCR3 documents for plan# {planNumber}, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetPCR3Documents"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/attendee")] + public async Task CreateAttendee(PCRBAttendee attendee) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to create new attendee"); + + if (attendee is null) throw new ArgumentNullException("attendee item cannot be null"); + + await _pcrbService.CreateNewAttendee(attendee); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot create new attendee, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "CreatePCRBAttendee"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPut] + [Route("pcrb/attendee")] + public async Task UpdateAttendee(PCRBAttendee attendee) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to update an attendee"); + + if (attendee is null) throw new ArgumentNullException("attendee cannot be null"); + + await _pcrbService.UpdateAttendee(attendee); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot update attendee, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "UpdatePCRBAttendee"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpDelete] + [Route("pcrb/attendee")] + public async Task DeleteAttendee(int id) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to delete an attendee"); + + if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB attendee ID"); + + await _pcrbService.DeleteAttendee(id); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot delete attendee, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "DeletePCRBAttendee"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpGet] + [Route("pcrb/attendees")] + public async Task GetAttendeesByPlanNumber(int planNumber, bool bypassCache) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation($"Attempting to get attendees for plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + List attendees = (await _pcrbService.GetAttendeesByPlanNumber(planNumber, bypassCache)).ToList(); + + return Ok(attendees); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Cannot get attendees for plan# {planNumber}, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "GetPCRBAttendees"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/notify/new-approvals")] + public async Task NotifyNewApprovals(PCRB pcrb) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to notify new approvers"); + + if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null"); + + await _pcrbService.NotifyNewApprovals(pcrb); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to notify new approvers, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "NotifyNewPCRBApprovers"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/notify/approvers")] + public async Task NotifyApprovers(PCRBNotification notification) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to notify approvers"); + + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty"); + + await _pcrbService.NotifyApprovers(notification); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to notify approvers, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "NotifyPCRBApprovers"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/notify/originator")] + public async Task NotifyOriginator(PCRBNotification notification) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to notify originator"); + + if (notification is null) throw new ArgumentNullException("MRBNotification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("Message cannot be null or empty"); + + await _pcrbService.NotifyOriginator(notification); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to notify originator, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "NotifyPCRBOriginator"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } + + [HttpPost] + [Route("pcrb/notify/responsiblePerson")] + public async Task NotifyResponsiblePerson(PCRBActionItemNotification notification) { + DateTime start = DateTime.Now; + bool isArgumentError = false; + bool isInternalError = false; + string errorMessage = ""; + + try { + _logger.LogInformation("Attempting to notify originator"); + + if (notification is null) throw new ArgumentNullException("MRBNotification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("Message cannot be null or empty"); + + await _pcrbService.NotifyResponsiblePerson(notification); + + return Ok(); + } catch (ArgumentException ex) { + isArgumentError = true; + errorMessage = ex.Message; + return BadRequest(errorMessage); + } catch (Exception ex) { + isInternalError = true; + errorMessage = $"Unable to notify responsible person, because {ex.Message}"; + return Problem(errorMessage); + } finally { + string metricName = "NotifyPCRBResponsiblePerson"; + DateTime end = DateTime.Now; + double millisecondsDiff = (end - start).TotalMilliseconds; + _monInClient.PostAverage(metricName + "Latency", millisecondsDiff); + + if (isArgumentError) { + _logger.LogWarning(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Ok); + } else if (isInternalError) { + _logger.LogError(errorMessage); + _monInClient.PostStatus(metricName, StatusValue.Critical); + } else { + _monInClient.PostStatus(metricName, StatusValue.Ok); + } + } + } } diff --git a/MesaFabApproval.API/MesaFabApproval.API.csproj b/MesaFabApproval.API/MesaFabApproval.API.csproj index a0c0f79..0c4532c 100644 --- a/MesaFabApproval.API/MesaFabApproval.API.csproj +++ b/MesaFabApproval.API/MesaFabApproval.API.csproj @@ -12,9 +12,9 @@ - - - + + + diff --git a/MesaFabApproval.API/Services/ApprovalService.cs b/MesaFabApproval.API/Services/ApprovalService.cs index 8cd6bbf..7e10c60 100644 --- a/MesaFabApproval.API/Services/ApprovalService.cs +++ b/MesaFabApproval.API/Services/ApprovalService.cs @@ -42,7 +42,7 @@ public class ApprovalService : IApprovalService { queryBuilder.Append("insert into Approval (IssueID, RoleName, SubRole, UserID, SubRoleID, ItemStatus, "); queryBuilder.Append("AssignedDate, DocumentTypeID, DisplayDeniedDocument, Step, TaskID) "); queryBuilder.Append($"values ({approval.IssueID}, '{approval.RoleName}', '{approval.SubRole}', {approval.UserID}, "); - queryBuilder.Append($"{approval.SubRoleID}, 0, '{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}', "); + queryBuilder.Append($"{approval.SubRoleID}, 0, '{approval.AssignedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); queryBuilder.Append($"3, 0, {approval.Step}, {approval.TaskID});"); int rowsCreated = await _dalService.ExecuteAsync(queryBuilder.ToString()); @@ -145,7 +145,7 @@ public class ApprovalService : IApprovalService { StringBuilder queryBuilder = new(); queryBuilder.Append("select src.SubRoleCategoryID, sr.SubRole as SubRoleName, src.SubRoleCategoryItem, sr.SubRoleID "); queryBuilder.Append("from SubRole sr join SubRoleCategory src on sr.SubRoleCategoryID=src.SubRoleCategoryID "); - queryBuilder.Append($"where sr.RoleID={roleId} and sr.SubRole='{subRoleName}'"); + queryBuilder.Append($"where sr.RoleID={roleId} and sr.SubRole='{subRoleName}' and sr.Inactive=0"); subRoles = (await _dalService.QueryAsync(queryBuilder.ToString())).ToList(); @@ -182,7 +182,7 @@ public class ApprovalService : IApprovalService { if (memberIds is null || memberIds.Count() <= 0) throw new Exception($"No members found in sub role {subRoleId}"); - _cache.Set($"approvalMemberIds{subRoleId}", memberIds, DateTimeOffset.Now.AddHours(1)); + _cache.Set($"approvalMemberIds{subRoleId}", memberIds, DateTimeOffset.Now.AddMinutes(5)); } members = new(); @@ -194,7 +194,7 @@ public class ApprovalService : IApprovalService { if (members.Count() <= 0) throw new Exception("No users found with IDs matching those found in SubRole"); - _cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddHours(1)); + _cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddMinutes(5)); } return members; @@ -249,7 +249,7 @@ public class ApprovalService : IApprovalService { queryBuilder.Append($"NotifyDate='{approval.NotifyDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); queryBuilder.Append($"AssignedDate='{approval.AssignedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); queryBuilder.Append($"CompletedDate='{approval.CompletedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); - queryBuilder.Append($"Comments='{approval.Comments}', "); + queryBuilder.Append($"Comments='{approval.Comments.Replace("'", "''")}', "); queryBuilder.Append($"TaskID={approval.TaskID} "); queryBuilder.Append($"where ApprovalID={approval.ApprovalID};"); diff --git a/MesaFabApproval.API/Services/AuthenticationService.cs b/MesaFabApproval.API/Services/AuthenticationService.cs index 801c594..99c3973 100644 --- a/MesaFabApproval.API/Services/AuthenticationService.cs +++ b/MesaFabApproval.API/Services/AuthenticationService.cs @@ -147,7 +147,7 @@ public class AuthenticationService : IAuthenticationService { Audience = _jwtAudience, Subject = identity, NotBefore = DateTime.Now, - Expires = DateTime.Now.AddHours(2), + Expires = DateTime.Now.AddHours(8), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; diff --git a/MesaFabApproval.API/Services/MRBService.cs b/MesaFabApproval.API/Services/MRBService.cs index 4e68119..3a5ae80 100644 --- a/MesaFabApproval.API/Services/MRBService.cs +++ b/MesaFabApproval.API/Services/MRBService.cs @@ -1,7 +1,9 @@ -using System.Net; +using System.Data; +using System.Net; using System.Net.Mail; using System.Text; +using MesaFabApproval.API.Utilities; using MesaFabApproval.Shared.Models; using MesaFabApproval.Shared.Utilities; @@ -30,6 +32,7 @@ public interface IMRBService { Task NotifyOriginator(MRBNotification notification); Task NotifyQAPreApprover(MRBNotification notification); Task DeleteMRB(int mrbNumber); + Task ConvertActionsToCsvFile(int mrbNumber, string path); } public class MRBService : IMRBService { @@ -130,9 +133,6 @@ public class MRBService : IMRBService { _cache.Set("allMrbs", allMrbs, DateTimeOffset.Now.AddHours(1)); } - if (allMrbs is null || allMrbs.Count() == 0) - throw new Exception("No MRBs found"); - return allMrbs; } catch (Exception ex) { _logger.LogError($"An exception occurred when attempting to get all MRBs. Exception: {ex.Message}"); @@ -236,7 +236,7 @@ public class MRBService : IMRBService { StringBuilder queryBuilder = new(); queryBuilder.Append($"update MRB set OriginatorID = {mrb.OriginatorID}, "); - queryBuilder.Append($"Title = '{mrb.Title}', "); + queryBuilder.Append($"Title = '{mrb.Title.Replace("'", "''")}', "); if (mrb.SubmittedDate < DateTimeUtilities.MIN_DT) mrb.SubmittedDate = DateTimeUtilities.MIN_DT; queryBuilder.Append($"SubmittedDate = '{mrb.SubmittedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); @@ -250,19 +250,19 @@ public class MRBService : IMRBService { if (mrb.ApprovalDate > DateTimeUtilities.MAX_DT) mrb.ApprovalDate = DateTimeUtilities.MAX_DT; queryBuilder.Append($"ApprovalDate = '{mrb.ApprovalDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); - queryBuilder.Append($"IssueDescription = '{mrb.IssueDescription}', "); + queryBuilder.Append($"IssueDescription = '{mrb.IssueDescription.Replace("'", "''")}', "); queryBuilder.Append($"CustomerImpacted = {Convert.ToInt32(mrb.CustomerImpacted)}, "); - queryBuilder.Append($"Department = '{mrb.Department}', "); - queryBuilder.Append($"Process = '{mrb.Process}', "); + queryBuilder.Append($"Department = '{mrb.Department.Replace("'", "''")}', "); + queryBuilder.Append($"Process = '{mrb.Process.Replace("'", "''")}', "); queryBuilder.Append($"Val = {mrb.Val}, "); queryBuilder.Append($"RMANo = {mrb.RMANo}, "); queryBuilder.Append($"PCRBNo = '{mrb.PCRBNo}', "); queryBuilder.Append($"SpecsImpacted = {Convert.ToInt32(mrb.SpecsImpacted)}, "); queryBuilder.Append($"TrainingRequired = {Convert.ToInt32(mrb.TrainingRequired)}, "); queryBuilder.Append($"Status = '{mrb.Status}', StageNo = {mrb.StageNo}, "); - queryBuilder.Append($"CustomerImpactedName = '{mrb.CustomerImpactedName}', "); + queryBuilder.Append($"CustomerImpactedName = '{mrb.CustomerImpactedName.Replace("'", "''")}', "); queryBuilder.Append($"ProcessECNNumber = '{mrb.ProcessECNNumber}', "); - queryBuilder.Append($"Tool = '{mrb.Tool}', Category = '{mrb.Category}' "); + queryBuilder.Append($"Tool = '{mrb.Tool.Replace("'", "''")}', Category = '{mrb.Category.Replace("'", "''")}' "); queryBuilder.Append($"where MRBNumber = {mrb.MRBNumber};"); int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); @@ -353,10 +353,10 @@ public class MRBService : IMRBService { StringBuilder queryBuilder = new(); queryBuilder.Append($"update MRBAction set Action = '{mrbAction.Action}', "); - queryBuilder.Append($"Customer = '{mrbAction.Customer}', "); + queryBuilder.Append($"Customer = '{mrbAction.Customer.Replace("'", "''")}', "); queryBuilder.Append($"Quantity = {mrbAction.Quantity}, "); - queryBuilder.Append($"PartNumber = '{mrbAction.PartNumber}', "); - queryBuilder.Append($"LotNumber = '{mrbAction.LotNumber}', "); + queryBuilder.Append($"PartNumber = '{mrbAction.PartNumber.Replace("'", "''")}', "); + queryBuilder.Append($"LotNumber = '{mrbAction.LotNumber.Replace("'", "''")}', "); if (mrbAction.AssignedDate < DateTimeUtilities.MIN_DT) mrbAction.AssignedDate = DateTimeUtilities.MIN_DT; queryBuilder.Append($"AssignedDate= '{mrbAction.AssignedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); @@ -364,9 +364,9 @@ public class MRBService : IMRBService { mrbAction.CompletedDate = DateTimeUtilities.MAX_DT; queryBuilder.Append($"CompletedDate= '{mrbAction.CompletedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); queryBuilder.Append($"CompletedByUserID={mrbAction.CompletedByUserID}, "); - queryBuilder.Append($"ConvertFrom='{mrbAction.ConvertFrom}', "); - queryBuilder.Append($"ConvertTo='{mrbAction.ConvertTo}', "); - queryBuilder.Append($"Justification='{mrbAction.Justification}' "); + queryBuilder.Append($"ConvertFrom='{mrbAction.ConvertFrom.Replace("'", "''")}', "); + queryBuilder.Append($"ConvertTo='{mrbAction.ConvertTo.Replace("'", "''")}', "); + queryBuilder.Append($"Justification='{mrbAction.Justification.Replace("'", "''")}' "); queryBuilder.Append($"where ActionID={mrbAction.ActionID};"); int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); @@ -427,7 +427,7 @@ public class MRBService : IMRBService { string encodedName = WebUtility.HtmlEncode(file.FileName); string path = $"{_mrbAttachmentPath}\\{mrbNumber}\\{encodedName}"; - await SaveFileToFileSystem(file, path); + await FileUtilities.SaveFileToFileSystem(file, path); await SaveAttachmentInDb(file, path, mrbNumber); UploadResult uploadResult = new() { @@ -471,7 +471,7 @@ public class MRBService : IMRBService { string encodedName = WebUtility.HtmlEncode(file.FileName); string path = $"{_mrbAttachmentPath}\\{actionId}\\{encodedName}"; - taskList.Add(SaveFileToFileSystem(file, path)); + taskList.Add(FileUtilities.SaveFileToFileSystem(file, path)); taskList.Add(SaveActionAttachmentInDb(file, path, actionId)); UploadResult uploadResult = new() { @@ -755,24 +755,138 @@ public class MRBService : IMRBService { } } - private async Task SaveFileToFileSystem(IFormFile file, string path) { + public async Task ConvertActionsToCsvFile(int mrbNumber, string path) { try { - _logger.LogInformation($"Attempting to save file to file system"); + _logger.LogInformation($"Attempting to convert MRB {mrbNumber} actions to a CSV file"); - if (file is null) throw new ArgumentNullException("File cannot be null"); + if (!(await MRBNumberIsValid(mrbNumber))) throw new ArgumentException($"{mrbNumber} is not a valid "); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Path cannot be null or empty"); - if (File.Exists(path)) throw new Exception($"A file already exists with name {file.FileName}"); + if (File.Exists(path)) File.Delete(path); string? directoryPath = Path.GetDirectoryName(path); if (!string.IsNullOrWhiteSpace(directoryPath)) Directory.CreateDirectory(directoryPath); - using (Stream stream = File.Create(path)) { - await file.CopyToAsync(stream); + IEnumerable actions = await GetMRBActionsForMRB(mrbNumber, false); + + DataTable dt = await ConvertActionsToDataTable(actions); + + using StreamWriter sw = new StreamWriter(path, false); + + for (int i = 0; i < dt.Columns.Count; i++) { + sw.Write(dt.Columns[i]); + if (i < dt.Columns.Count - 1) sw.Write(","); } + + sw.Write(sw.NewLine); + + foreach (DataRow dr in dt.Rows) { + for (int i = 0; i < dt.Columns.Count; i++) { + if (!Convert.IsDBNull(dr[i])) { + string? value = dr[i].ToString(); + if (value is null) { + sw.Write(""); + } else if (value.Contains(',')) { + value = String.Format("\"{0}\"", value); + sw.Write(value); + } else { + sw.Write(dr[i].ToString()); + } + } + + if (i < dt.Columns.Count - 1) { + sw.Write(","); + } + } + + sw.Write(sw.NewLine); + } + + sw.Close(); } catch (Exception ex) { - _logger.LogError($"An exception occurred when attempting to save file to file system. Exception: {ex.Message}"); + _logger.LogError($"Unable to convert MRB {mrbNumber} actions to a CSV file, because {ex.Message}"); + throw; + } + } + + private async Task ConvertActionsToDataTable(IEnumerable actions) { + try { + _logger.LogInformation("Attempting to convert MRB actions to a DataTable"); + + if (actions is null) throw new ArgumentNullException("MRB actions cannot be null"); + + DataTable dt = new(); + + if (actions.Count() > 0 && actions.First().Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) { + dt.Columns.Add("Action", typeof(string)); + dt.Columns.Add("From Customer", typeof(string)); + dt.Columns.Add("From Part Number", typeof(string)); + dt.Columns.Add("Batch Number / Lot Number", typeof(string)); + dt.Columns.Add("Qty", typeof(string)); + dt.Columns.Add("To Customer", typeof(string)); + dt.Columns.Add("To Part Number", typeof(string)); + dt.Columns.Add("Assigned Date", typeof(string)); + dt.Columns.Add("Completed Date", typeof(string)); + dt.Columns.Add("Completed By", typeof(string)); + + foreach (MRBAction action in actions) { + if (action.CompletedByUser is null && action.CompletedByUserID > 0) + action.CompletedByUser = await _userService.GetUserByUserId(action.CompletedByUserID); + + string convertFromCustomer = string.Empty; + string convertFromPart = string.Empty; + string convertToCustomer = string.Empty; + string convertToPart = string.Empty; + + string[] convertFrom = action.ConvertFrom.Split(" "); + if (convertFrom.Length > 1) { + convertFromCustomer = convertFrom[0]; + foreach (string partStr in convertFrom.Skip(1)) + convertFromPart += partStr; + } + + string[] convertTo = action.ConvertTo.Split(" "); + if (convertTo.Length > 1) { + convertToCustomer = convertTo[0]; + foreach (string partStr in convertTo.Skip(1)) + convertToPart += partStr; + } + + dt.Rows.Add(action.Action, convertFromCustomer, convertFromPart, action.Quantity.ToString(), + convertToCustomer, convertToPart, + DateTimeUtilities.GetDateAsStringMinDefault(action.AssignedDate), + DateTimeUtilities.GetDateAsStringMaxDefault(action.CompletedDate), + action.CompletedByUser is null ? "" : action.CompletedByUser.GetFullName()); + } + } else { + dt.Columns.Add("Action", typeof(string)); + dt.Columns.Add("Customer", typeof(string)); + dt.Columns.Add("Qty", typeof(string)); + dt.Columns.Add("Convert From", typeof(string)); + dt.Columns.Add("Convert To", typeof(string)); + dt.Columns.Add("Part Number", typeof(string)); + dt.Columns.Add("Batch Number / Lot Number", typeof(string)); + dt.Columns.Add("Justification", typeof(string)); + dt.Columns.Add("Assigned Date", typeof(string)); + dt.Columns.Add("Completed Date", typeof(string)); + dt.Columns.Add("Completed By", typeof(string)); + + foreach (MRBAction action in actions) { + if (action.CompletedByUser is null && action.CompletedByUserID > 0) + action.CompletedByUser = await _userService.GetUserByUserId(action.CompletedByUserID); + + dt.Rows.Add(action.Action, action.Customer, action.Quantity.ToString(), action.ConvertFrom, action.ConvertTo, + action.PartNumber, action.LotNumber, action.Justification, + DateTimeUtilities.GetDateAsStringMinDefault(action.AssignedDate), + DateTimeUtilities.GetDateAsStringMaxDefault(action.CompletedDate), + action.CompletedByUser is null ? "" : action.CompletedByUser.GetFullName()); + } + } + + return dt; + } catch (Exception ex) { + _logger.LogError($"Unable to convert MRB actions to a DataTable, because {ex.Message}"); throw; } } diff --git a/MesaFabApproval.API/Services/PCRBService.cs b/MesaFabApproval.API/Services/PCRBService.cs index b823e76..228214b 100644 --- a/MesaFabApproval.API/Services/PCRBService.cs +++ b/MesaFabApproval.API/Services/PCRBService.cs @@ -1,18 +1,41 @@ -using System.Text; +using System.Net; +using System.Net.Mail; +using System.Text; +using MesaFabApproval.API.Utilities; using MesaFabApproval.Shared.Models; +using MesaFabApproval.Shared.Utilities; using Microsoft.Extensions.Caching.Memory; namespace MesaFabApproval.API.Services; public interface IPCRBService { - public Task CreateNewPCRB(PCRB pcrb); - public Task> GetAllPCRBs(bool bypassCache); - public Task GetPCRBByPlanNumber(int planNumber, bool bypassCache); - public Task GetPCRBByTitle(string title, bool bypassCache); - public Task UpdatePCRB(PCRB pcrb); - public Task DeletePCRB(int planNumber); + Task CreateNewPCRB(PCRB pcrb); + Task> GetAllPCRBs(bool bypassCache); + Task GetPCRBByPlanNumber(int planNumber, bool bypassCache); + Task GetPCRBByTitle(string title, bool bypassCache); + Task UpdatePCRB(PCRB pcrb); + Task DeletePCRB(int planNumber); + Task UploadAttachment(IFormFile file, PCRBAttachment attachment); + Task> GetAttachmentsByPlanNumber(int planNumber, bool bypassCache); + Task UpdateAttachment(PCRBAttachment attachment); + Task DeleteAttachment(PCRBAttachment attachment); + Task CreateNewActionItem(PCRBActionItem actionItem); + Task UpdateActionItem(PCRBActionItem actionItem); + Task DeleteActionItem(int id); + Task> GetActionItemsForPlanNumber(int planNumber, bool bypassCache); + Task CreateNewAttendee(PCRBAttendee attendee); + Task UpdateAttendee(PCRBAttendee attendee); + Task DeleteAttendee(int id); + Task> GetAttendeesByPlanNumber(int planNumber, bool bypassCache); + Task CreatePCR3Document(PCR3Document document); + Task UpdatePCR3Document(PCR3Document document); + Task> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache); + Task NotifyNewApprovals(PCRB pcrb); + Task NotifyApprovers(PCRBNotification notification); + Task NotifyOriginator(PCRBNotification notification); + Task NotifyResponsiblePerson(PCRBActionItemNotification notification); } public class PCRBService : IPCRBService { @@ -20,16 +43,29 @@ public class PCRBService : IPCRBService { private readonly IDalService _dalService; private readonly IMemoryCache _cache; private readonly IUserService _userService; - + private readonly IApprovalService _approvalService; + private readonly ISmtpService _smtpService; + + private readonly string _pcrbAttachmentPath; + private readonly string _siteBaseUrl; public PCRBService(ILogger logger, IDalService dalService, IMemoryCache cache, - IUserService userService) { + IUserService userService, + IApprovalService approvalService, + ISmtpService smtpService) { _logger = logger ?? throw new ArgumentNullException("ILogger not injected"); _dalService = dalService ?? throw new ArgumentNullException("IDalService not injected"); _cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected"); _userService = userService ?? throw new ArgumentNullException("IUserService not injected"); + _pcrbAttachmentPath = Environment.GetEnvironmentVariable("FabApprovalPcrbAttachmentPath") ?? + throw new ArgumentNullException("FabApprovalPcrbAttachmentPath environment variable not found"); + _approvalService = approvalService ?? + throw new ArgumentNullException("IApprovalService not injected"); + _smtpService = smtpService ?? throw new ArgumentNullException("ISmtpService not injected"); + _siteBaseUrl = Environment.GetEnvironmentVariable("NewFabApprovalBaseUrl") ?? + throw new ArgumentNullException("FabApprovalBaseUrl environment variable not found"); } public async Task CreateNewPCRB(PCRB pcrb) { @@ -160,11 +196,12 @@ public class PCRBService : IPCRBService { StringBuilder queryBuilder = new(); queryBuilder.Append($"update CCChangeControl set OwnerID={pcrb.OwnerID}, "); - queryBuilder.Append($"Title='{pcrb.Title}', ChangeLevel='{pcrb.ChangeLevel}', "); - queryBuilder.Append($"CurrentStep={pcrb.CurrentStep}, ReasonForChange='{pcrb.ReasonForChange}', "); - queryBuilder.Append($"ChangeDescription='{pcrb.ChangeDescription}', "); + queryBuilder.Append($"Title='{pcrb.Title.Replace("'", "''")}', ChangeLevel='{pcrb.ChangeLevel}', "); + queryBuilder.Append($"CurrentStep={pcrb.CurrentStep}, ReasonForChange='{pcrb.ReasonForChange.Replace("'", "''")}', "); + queryBuilder.Append($"ChangeDescription='{pcrb.ChangeDescription.Replace("'", "''")}', "); queryBuilder.Append($"IsITAR={Convert.ToInt32(pcrb.IsITAR)}, "); queryBuilder.Append($"InsertTimeStamp='{pcrb.InsertTimeStamp.ToString("yyyy-MM-dd HH:mm:ss")}', "); + queryBuilder.Append($"ClosedDate='{pcrb.ClosedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); queryBuilder.Append($"LastUpdateDate='{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}' "); queryBuilder.Append($"where PlanNumber={pcrb.PlanNumber}"); @@ -211,4 +248,520 @@ public class PCRBService : IPCRBService { throw; } } + + public async Task UploadAttachment(IFormFile file, PCRBAttachment attachment) { + try { + _logger.LogInformation("Attempting to upload attachment"); + + UploadResult? uploadResult = null; + + if (file is null) throw new ArgumentNullException("File cannot be null"); + if (file.Length <= 0) throw new ArgumentException("File size cannot be zero"); + if (attachment is null) throw new ArgumentNullException("Attachment cannot be null"); + + try { + string encodedName = WebUtility.HtmlEncode(file.FileName); + string path = $"{_pcrbAttachmentPath}\\{attachment.PlanNumber}\\{attachment.Step}\\{encodedName}"; + + attachment.Path = path; + + await FileUtilities.SaveFileToFileSystem(file, path); + await SaveAttachmentInDb(file, attachment); + + uploadResult = new() { + UploadSuccessful = true, + FileName = file.FileName + }; + } catch (Exception ex) { + uploadResult = new() { + UploadSuccessful = false, + FileName = file.FileName, + Error = ex.Message + }; + } + + return uploadResult; + } catch (Exception ex) { + _logger.LogError($"Unable to upload attachment, because {ex.Message}"); + throw; + } + } + + public async Task> GetAttachmentsByPlanNumber(int planNumber, bool bypassCache) { + try { + _logger.LogInformation($"Attempting to get all attachments for PCRB Plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + IEnumerable? attachments = null; + + if (!bypassCache) + attachments = _cache.Get>($"pcrbAttachments{planNumber}"); + + if (attachments is null) { + string sql = $"select * from CCAttachment where PlanNumber={planNumber}"; + + attachments = await _dalService.QueryAsync(sql); + + _cache.Set($"pcrbAttachments{planNumber}", attachments, DateTimeOffset.Now.AddMinutes(15)); + } + + return attachments; + } catch (Exception ex) { + _logger.LogError($"Unable to get all attachments for PCRB Plan# {planNumber}, because {ex.Message}"); + throw; + } + } + + public async Task UpdateAttachment(PCRBAttachment attachment) { + try { + _logger.LogInformation("Attempting to update an attachment"); + + if (attachment is null) + throw new ArgumentNullException("attachment cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append($"update CCAttachment "); + queryBuilder.Append($"set Title='{attachment.Title.Replace("'", "''")}' where ID={attachment.ID}"); + + int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsAffected <= 0) throw new Exception("update failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to update attachment, because {ex.Message}"); + throw; + } + } + + public async Task DeleteAttachment(PCRBAttachment attachment) { + try { + _logger.LogInformation("Attempting to update an attachment"); + + if (attachment is null) + throw new ArgumentNullException("attachment cannot be null"); + + if (!File.Exists(attachment.Path)) throw new FileNotFoundException("No file found at provided path"); + + File.Delete(attachment.Path); + + string sql = $"delete from CCAttachment where ID={attachment.ID}"; + + int rowsAffected = await _dalService.ExecuteAsync(sql); + + if (rowsAffected <= 0) throw new Exception("update failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to update attachment, because {ex.Message}"); + throw; + } + } + + public async Task CreateNewActionItem(PCRBActionItem actionItem) { + try { + _logger.LogInformation("Attempting to create new action item"); + + if (actionItem is null) throw new ArgumentNullException("action item cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("insert into CCPCRBActionItem (Name, Gating, ClosedStatus, ClosedDate, "); + queryBuilder.Append("ClosedByID, UploadedByID, UploadedDateTime, ResponsiblePersonID, PlanNumber, "); + queryBuilder.Append($"Step) values ('{actionItem.Name}', {Convert.ToInt32(actionItem.Gating)}, "); + queryBuilder.Append($"{Convert.ToInt32(actionItem.ClosedStatus)}, "); + DateTime closedDateCopy = actionItem.ClosedDate ?? DateTimeUtilities.MAX_DT; + queryBuilder.Append($"'{closedDateCopy.ToString("yyyy-MM-dd HH:mm:ss")}', {actionItem.ClosedByID}, "); + queryBuilder.Append($"{actionItem.UploadedByID}, '{actionItem.UploadedDateTime.ToString("yyyy-MM-dd HH:mm:ss")}', "); + queryBuilder.Append($"{actionItem.ResponsiblePersonID}, {actionItem.PlanNumber}, "); + queryBuilder.Append($"{actionItem.Step});"); + + int rowsCreated = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsCreated <= 0) throw new Exception("unable to insert new action item in the database"); + } catch (Exception ex) { + _logger.LogError($"Unable to create new action item, because {ex.Message}"); + throw; + } + } + + public async Task UpdateActionItem(PCRBActionItem actionItem) { + try { + _logger.LogInformation("Attempting to update an action item"); + + if (actionItem is null) + throw new ArgumentNullException("action item cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append($"update CCPCRBActionItem set Name='{actionItem.Name.Replace("'", "''")}', Gating={Convert.ToInt32(actionItem.Gating)}, "); + queryBuilder.Append($"ClosedStatus={Convert.ToInt32(actionItem.ClosedStatus)}, "); + DateTime closedDateCopy = actionItem.ClosedDate ?? DateTimeUtilities.MAX_DT; + queryBuilder.Append($"ClosedDate='{closedDateCopy.ToString("yyyy-MM-dd HH:mm:ss")}', "); + queryBuilder.Append($"ClosedByID={actionItem.ClosedByID}, ResponsiblePersonID={actionItem.ResponsiblePersonID}, "); + queryBuilder.Append($"Step={actionItem.Step} where ID={actionItem.ID}"); + + int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsAffected <= 0) throw new Exception("update failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to update attachment, because {ex.Message}"); + throw; + } + } + + public async Task DeleteActionItem(int id) { + try { + _logger.LogInformation($"Attempting to delete action item {id}"); + + if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB action item ID"); + + string sql = $"delete from CCPCRBActionItem where ID={id}"; + + int rowsAffected = await _dalService.ExecuteAsync(sql); + + if (rowsAffected <= 0) throw new Exception("delete operation failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to delete action item {id}, because {ex.Message}"); + throw; + } + } + + public async Task> GetActionItemsForPlanNumber(int planNumber, bool bypassCache) { + try { + _logger.LogInformation($"Attempting to get all action items for PCRB plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB plan#"); + + IEnumerable? actionItems = null; + + if (!bypassCache) + actionItems = _cache.Get>($"pcrbActionItems{planNumber}"); + + if (actionItems is null) { + string sql = $"select * from CCPCRBActionItem where PlanNumber={planNumber}"; + + actionItems = await _dalService.QueryAsync(sql); + + _cache.Set($"pcrbActionItems{planNumber}", actionItems, DateTimeOffset.Now.AddMinutes(15)); + } + + return actionItems; + } catch (Exception ex) { + _logger.LogError($"Unable to get all action items for PCRB plan# {planNumber}, because {ex.Message}"); + throw; + } + } + + public async Task CreateNewAttendee(PCRBAttendee attendee) { + try { + _logger.LogInformation("Attempting to create new attendee"); + + if (attendee is null) throw new ArgumentNullException("attendee item cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("insert into CCPCRBAttendee (PlanNumber, JobTitle, Location, Attended, AttendeeID, Step) "); + queryBuilder.Append($"values ({attendee.PlanNumber}, '{attendee.JobTitle}', '{attendee.Location}', "); + queryBuilder.Append($"{Convert.ToInt32(attendee.Attended)}, {attendee.AttendeeID}, "); + queryBuilder.Append($"{attendee.Step});"); + + int rowsCreated = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsCreated <= 0) throw new Exception("unable to insert new attendee in the database"); + } catch (Exception ex) { + _logger.LogError($"Unable to create new attendee, because {ex.Message}"); + throw; + } + } + + public async Task UpdateAttendee(PCRBAttendee attendee) { + try { + _logger.LogInformation("Attempting to update an attendee"); + + if (attendee is null) + throw new ArgumentNullException("attendee cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append($"update CCPCRBAttendee set JobTitle='{attendee.JobTitle}', "); + queryBuilder.Append($"Location='{attendee.Location}', Attended={Convert.ToInt32(attendee.Attended)}, "); + queryBuilder.Append($"AttendeeID={attendee.AttendeeID}, "); + queryBuilder.Append($"Step={attendee.Step} where ID={attendee.ID}"); + + int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsAffected <= 0) throw new Exception("update failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to update attendee, because {ex.Message}"); + throw; + } + } + + public async Task DeleteAttendee(int id) { + try { + _logger.LogInformation($"Attempting to delete attendee {id}"); + + if (id <= 0) throw new ArgumentException($"{id} is not a valid attendee ID"); + + string sql = $"delete from CCPCRBAttendee where ID={id}"; + + int rowsAffected = await _dalService.ExecuteAsync(sql); + + if (rowsAffected <= 0) throw new Exception("delete operation failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to delete attendee {id}, because {ex.Message}"); + throw; + } + } + + public async Task> GetAttendeesByPlanNumber(int planNumber, bool bypassCache) { + try { + _logger.LogInformation($"Attempting to get all attendees for PCRB plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB plan#"); + + IEnumerable? attendees = null; + + if (!bypassCache) + attendees = _cache.Get>($"pcrbAttendees{planNumber}"); + + if (attendees is null) { + string sql = $"select * from CCPCRBAttendee where PlanNumber={planNumber}"; + + attendees = await _dalService.QueryAsync(sql); + + _cache.Set($"pcrbAttendees{planNumber}", attendees, DateTimeOffset.Now.AddMinutes(15)); + } + + return attendees; + } catch (Exception ex) { + _logger.LogError($"Unable to get all attendees for PCRB plan# {planNumber}, because {ex.Message}"); + throw; + } + } + + public async Task CreatePCR3Document(PCR3Document document) { + try { + _logger.LogInformation("Attempting to create new PCR3 document"); + + if (document is null) throw new ArgumentNullException("document item cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("insert into CCPCR3Document (PlanNumber, DocType) "); + queryBuilder.Append($"values ({document.PlanNumber}, '{document.DocType}')"); + + int rowsCreated = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsCreated <= 0) throw new Exception("unable to insert new PCR3 document in the database"); + } catch (Exception ex) { + _logger.LogError($"Unable to create new PCR3 document, because {ex.Message}"); + throw; + } + } + + public async Task UpdatePCR3Document(PCR3Document document) { + try { + _logger.LogInformation("Attempting to update a PCR3 document"); + + if (document is null) throw new ArgumentNullException("document cannot be null"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append($"update CCPCR3Document set DocNumbers='{document.DocNumbers}', "); + queryBuilder.Append($"Comment='{document.Comment.Replace("'", "''")}', ECNNumber={document.ECNNumber}, "); + queryBuilder.Append($"CompletedDate='{document.CompletedDate.ToString("yyyy-MM-dd HH:mm:ss")}', "); + queryBuilder.Append($"CompletedByID={document.CompletedByID} "); + queryBuilder.Append($"where ID={document.ID}"); + + int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsAffected <= 0) throw new Exception("update failed in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to update PCR3 document, because {ex.Message}"); + throw; + } + } + + public async Task> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache) { + try { + _logger.LogInformation($"Attempting to get all PCR3 documents for PCRB plan# {planNumber}"); + + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB plan#"); + + IEnumerable? documents = null; + + if (!bypassCache) + documents = _cache.Get>($"pcr3Documents{planNumber}"); + + if (documents is null) { + string sql = $"select * from CCPCR3Document where PlanNumber={planNumber}"; + + documents = await _dalService.QueryAsync(sql); + + _cache.Set($"pcr3Documents{planNumber}", documents, DateTimeOffset.Now.AddMinutes(15)); + } + + return documents; + } catch (Exception ex) { + _logger.LogError($"Unable to get all PCR3 documents for PCRB plan# {planNumber}, because {ex.Message}"); + throw; + } + } + + public async Task NotifyNewApprovals(PCRB pcrb) { + try { + _logger.LogInformation("Attempting to notify approvers"); + + if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null"); + + IEnumerable approvals = await _approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, true); + + List approvalsNeedingNotification = approvals.Where(a => a.Step == pcrb.CurrentStep && + a.NotifyDate <= DateTimeUtilities.MIN_DT && + a.AssignedDate > DateTimeUtilities.MIN_DT).ToList(); + + HashSet emailsAlreadySent = new(); + foreach (Approval approval in approvalsNeedingNotification) { + User user = await _userService.GetUserByUserId(approval.UserID); + + if (!emailsAlreadySent.Contains(user.Email.ToLower())) { + emailsAlreadySent.Add(user.Email); + + List toAddresses = new(); + toAddresses.Add(new MailAddress(user.Email.ToLower())); + + List ccAddresses = new(); + + string subject = $"[New Task] Mesa Fab Approval - PCRB# {pcrb.PlanNumber} - {pcrb.Title}"; + + StringBuilder bodyBuilder = new(); + bodyBuilder.Append($"PCRB# {pcrb.PlanNumber} [{pcrb.Title}] PCR{approval.Step} is ready for your approval. "); + bodyBuilder.Append($"The assigned role is {approval.SubRoleCategoryItem}.

    "); + bodyBuilder.Append($"Click {_siteBaseUrl}/redirect?redirectPath=pcrb/{approval.IssueID} to view the PCRB."); + + await _smtpService.SendEmail(toAddresses, ccAddresses, subject, bodyBuilder.ToString()); + + approval.NotifyDate = DateTime.Now; + await _approvalService.UpdateApproval(approval); + } + } + } catch (Exception ex) { + _logger.LogError($"Unable to notify approvers, because {ex.Message}"); + throw; + } + } + + public async Task NotifyApprovers(PCRBNotification notification) { + try { + _logger.LogInformation("Attempting to send notification to approvers"); + + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty"); + + IEnumerable approvals = await _approvalService.GetApprovalsForIssueId(notification.PCRB.PlanNumber, true); + + HashSet emailsAlreadySent = new(); + foreach (Approval approval in approvals) { + User user = await _userService.GetUserByUserId(approval.UserID); + + if (!emailsAlreadySent.Contains(user.Email)) { + emailsAlreadySent.Add(user.Email); + + List toAddresses = new(); + toAddresses.Add(new MailAddress(user.Email)); + + List ccAddresses = new(); + + string subject = $"[Update] Mesa Fab Approval - PCRB# {notification.PCRB.PlanNumber} - {notification.PCRB.Title}"; + + StringBuilder bodyBuilder = new(); + bodyBuilder.Append($"{notification.Message}

    "); + bodyBuilder.Append($"Click {_siteBaseUrl}/redirect?redirectPath=pcrb/{approval.IssueID} to view the PCRB."); + + await _smtpService.SendEmail(toAddresses, ccAddresses, subject, bodyBuilder.ToString()); + + approval.NotifyDate = DateTime.Now; + await _approvalService.UpdateApproval(approval); + } + } + } catch (Exception ex) { + _logger.LogError($"Unable to send notification to originator, because {ex.Message}"); + throw; + } + } + + public async Task NotifyOriginator(PCRBNotification notification) { + try { + _logger.LogInformation("Attempting to send notification to originator"); + + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty"); + + User user = await _userService.GetUserByUserId(notification.PCRB.OwnerID); + + List toAddresses = new(); + toAddresses.Add(new MailAddress(user.Email)); + + List ccAddresses = new(); + + string subject = $"[Update] Mesa Fab Approval - PCRB# {notification.PCRB.PlanNumber} - {notification.PCRB.Title}"; + + StringBuilder bodyBuilder = new(); + bodyBuilder.Append($"{notification.Message}

    "); + bodyBuilder.Append($"Click {_siteBaseUrl}/redirect?redirectPath=pcrb/{notification.PCRB.PlanNumber} to view the PCRB."); + + await _smtpService.SendEmail(toAddresses, ccAddresses, subject, bodyBuilder.ToString()); + } catch (Exception ex) { + _logger.LogError($"Unable to send notification to originator, because {ex.Message}"); + throw; + } + } + + public async Task NotifyResponsiblePerson(PCRBActionItemNotification notification) { + try { + _logger.LogInformation("Attempting to notify responsible person"); + + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) + throw new ArgumentException("message cannot be null or empty"); + + if (notification.ActionItem.ResponsiblePerson is null) + notification.ActionItem.ResponsiblePerson = await _userService.GetUserByUserId(notification.ActionItem.ResponsiblePersonID); + + List toAddresses = new(); + toAddresses.Add(new MailAddress(notification.ActionItem.ResponsiblePerson.Email)); + + List ccAddresses = new(); + + string subject = $"[New Task] Mesa Fab Approval - PCRB# {notification.PCRB.PlanNumber} - {notification.PCRB.Title}"; + + StringBuilder bodyBuilder = new(); + bodyBuilder.Append($"{notification.Message}

    "); + bodyBuilder.Append($"Click {_siteBaseUrl}/redirect?redirectPath=pcrb/{notification.PCRB.PlanNumber} to view the PCRB."); + + await _smtpService.SendEmail(toAddresses, ccAddresses, subject, bodyBuilder.ToString()); + } catch (Exception ex) { + _logger.LogError($"Unable to notify responsible person, because {ex.Message}"); + throw; + } + } + + private async Task SaveAttachmentInDb(IFormFile file, PCRBAttachment attachment) { + try { + _logger.LogInformation($"Attempting to save attachment to database"); + + if (file is null) throw new ArgumentNullException("File cannot be null"); + if (string.IsNullOrWhiteSpace(attachment.Path)) throw new ArgumentException("Path cannot be null or empty"); + if (attachment.PlanNumber <= 0) throw new ArgumentException($"{attachment.PlanNumber} is not a valid PCRB Plan#"); + + StringBuilder queryBuilder = new(); + queryBuilder.Append("insert into CCAttachment (PlanNumber, FileName, UploadDateTime, Path, UploadedByID, Title, "); + queryBuilder.Append($"Step) values ({attachment.PlanNumber}, '{file.FileName}', "); + queryBuilder.Append($"'{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}', '{attachment.Path}', {attachment.UploadedByID}, "); + queryBuilder.Append($"'{attachment.Title}', {attachment.Step});"); + + int rowsAffected = await _dalService.ExecuteAsync(queryBuilder.ToString()); + + if (rowsAffected <= 0) + throw new Exception("Unable to insert attachment in database"); + } catch (Exception ex) { + _logger.LogError($"Unable to save file to DB, because {ex.Message}"); + throw; + } + } } diff --git a/MesaFabApproval.API/Utilities/FileUtilities.cs b/MesaFabApproval.API/Utilities/FileUtilities.cs new file mode 100644 index 0000000..cdd7e59 --- /dev/null +++ b/MesaFabApproval.API/Utilities/FileUtilities.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Components.Forms; + +using NLog; + +namespace MesaFabApproval.API.Utilities; + +public class FileUtilities { + private static readonly Logger _logger = NLog.LogManager.GetCurrentClassLogger(); + + public static async Task SaveFileToFileSystem(IFormFile file, string path) { + try { + _logger.Info($"Attempting to save file to file system"); + + if (file is null) throw new ArgumentNullException("File cannot be null"); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("Path cannot be null or empty"); + + if (File.Exists(path)) throw new Exception($"A file already exists with name {file.FileName}"); + + string? directoryPath = Path.GetDirectoryName(path); + if (!string.IsNullOrWhiteSpace(directoryPath)) + Directory.CreateDirectory(directoryPath); + + using (Stream stream = File.Create(path)) { + await file.CopyToAsync(stream); + } + } catch (Exception ex) { + _logger.Error($"Unable to save file to file system, because {ex.Message}"); + throw; + } + } +} diff --git a/MesaFabApproval.API/nLog.config b/MesaFabApproval.API/nLog.config index 643f25f..28f2dc6 100644 --- a/MesaFabApproval.API/nLog.config +++ b/MesaFabApproval.API/nLog.config @@ -28,6 +28,6 @@ - + \ No newline at end of file diff --git a/MesaFabApproval.Client/Layout/MainLayout.razor b/MesaFabApproval.Client/Layout/MainLayout.razor index 3390354..8c3ed08 100644 --- a/MesaFabApproval.Client/Layout/MainLayout.razor +++ b/MesaFabApproval.Client/Layout/MainLayout.razor @@ -1,8 +1,14 @@ -@inherits LayoutComponentBase +@using System.Text.Json +@using System.Text +@inherits LayoutComponentBase @inject MesaFabApprovalAuthStateProvider authStateProvider +@inject IAuthenticationService authenticationService @inject IConfiguration Configuration @inject IMemoryCache cache +@inject IJSRuntime jsRuntime +@inject IHttpClientFactory httpClientFactory +@inject ISnackbar snackbar @inject NavigationManager navManager @@ -28,19 +34,22 @@ + Color="Color.Tertiary" + Href="@Configuration["OldFabApprovalUrl"]" + Target="_blank" + StartIcon="@Icons.Material.Filled.Home"> Return to Main Site + @if (authStateProvider.CurrentUser is not null) { Create New MRB + Create New PCRB Dashboard MRB List + PCRB List } @@ -68,4 +77,48 @@ cache.Set("redirectUrl", page); navManager.NavigateTo(page); } + + private async Task GoToExternal(string url, string content) { + IJSObjectReference windowModule = await jsRuntime.InvokeAsync("import", "./js/OpenInNewWindow.js"); + await windowModule.InvokeAsync("OpenInNewWindow", url, content); + } + + private async Task GoToOldSite() { + try { + User? currentUser = authStateProvider.CurrentUser; + + AuthTokens? authTokens = await authenticationService.GetAuthTokens(); + + if (currentUser is null || authTokens is null) { + await authStateProvider.Logout(); + navManager.NavigateTo("login"); + return; + } + + AuthAttempt authAttempt = new() { + LoginID = currentUser.LoginID, + AuthTokens = authTokens + }; + + HttpClient httpClient = httpClientFactory.CreateClient("OldSite"); + + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "Account/ExternalAuthSetup"); + + request.Content = new StringContent(JsonSerializer.Serialize(authAttempt), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage httpResponseMessage = await httpClient.SendAsync(request); + + if (httpResponseMessage.IsSuccessStatusCode) { + snackbar.Add("Old site auth setup successful", Severity.Success); + } else { + snackbar.Add($"Old site auth setup failed, because {httpResponseMessage.ReasonPhrase}", Severity.Error); + } + + await GoToExternal($"{Configuration["OldFabApprovalUrl"]}", ""); + } catch (Exception ex) { + snackbar.Add($"Unable to go to old site, because {ex.Message}", Severity.Error); + } + } } \ No newline at end of file diff --git a/MesaFabApproval.Client/MesaFabApproval.Client.csproj b/MesaFabApproval.Client/MesaFabApproval.Client.csproj index f773ec9..cdfb8e2 100644 --- a/MesaFabApproval.Client/MesaFabApproval.Client.csproj +++ b/MesaFabApproval.Client/MesaFabApproval.Client.csproj @@ -10,6 +10,10 @@ + + + + @@ -17,7 +21,7 @@ - + diff --git a/MesaFabApproval.Client/Pages/AuthenticatedRedirect.razor b/MesaFabApproval.Client/Pages/AuthenticatedRedirect.razor index e9ececd..352595a 100644 --- a/MesaFabApproval.Client/Pages/AuthenticatedRedirect.razor +++ b/MesaFabApproval.Client/Pages/AuthenticatedRedirect.razor @@ -25,7 +25,7 @@ } if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("redirectPath", out var redirectPath)) { - _redirectPath = System.Net.WebUtility.UrlDecode(redirectPath); + _redirectPath = redirectPath.ToString(); } if (!string.IsNullOrWhiteSpace(_jwt) && !string.IsNullOrWhiteSpace(_refreshToken)) { @@ -35,11 +35,12 @@ string loginId = userService.GetLoginIdFromClaimsPrincipal(principal); + await authService.ClearCurrentUser(); + await authService.ClearTokens(); + await authService.SetLoginId(loginId); await authService.SetTokens(_jwt, _refreshToken); - - User? user = await userService.GetUserByLoginId(loginId); - await authService.SetCurrentUser(user); + await authService.SetCurrentUser(null); await authStateProvider.StateHasChanged(principal); } diff --git a/MesaFabApproval.Client/Pages/Components/Comments.razor b/MesaFabApproval.Client/Pages/Components/Comments.razor index 0675477..9ecdaad 100644 --- a/MesaFabApproval.Client/Pages/Components/Comments.razor +++ b/MesaFabApproval.Client/Pages/Components/Comments.razor @@ -2,7 +2,7 @@ - + - + - + - @if (mrbAction.Action.Equals("Convert")) { - + @foreach (string customer in customerNames) { + + } + + - + + @foreach (string customer in customerNames) { + + } + + + RequiredError="Part number required!" + Text="@convertToPart" /> + } else { + + @foreach (string customer in customerNames) { + + } + + } - - @foreach (string customer in customerNames) { - - } - - customerNames = new List(); + private string convertFromCustomer = string.Empty; + private string convertFromPart = string.Empty; + private string convertToCustomer = string.Empty; + private string convertToPart = string.Empty; + private bool isVisible { get; set; } private string[] errors = { }; private bool processingSave = false; @@ -145,10 +174,34 @@ actions = (await mrbService.GetMRBActionsForMRB(mrbAction.MRBNumber, false)).OrderByDescending(a => a.ActionID); if (actions is not null && actions.Count() > 0) { + if (string.IsNullOrWhiteSpace(mrbAction.Action)) + mrbAction.Action = actions.First().Action; + if (string.IsNullOrWhiteSpace(mrbAction.Customer)) + mrbAction.Customer = actions.First().Customer; if (string.IsNullOrWhiteSpace(mrbAction.LotNumber)) mrbAction.LotNumber = actions.First().LotNumber; if (string.IsNullOrWhiteSpace(mrbAction.PartNumber)) mrbAction.PartNumber = actions.First().PartNumber; + if (string.IsNullOrWhiteSpace(mrbAction.Justification)) + mrbAction.Justification = actions.First().Justification; + if (mrbAction.Quantity == 0) + mrbAction.Quantity = actions.First().Quantity; + + if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) { + string[] convertFrom = actions.First().ConvertFrom.Split(" "); + if (convertFrom.Length > 1) { + convertFromCustomer = convertFrom[0]; + foreach (string partStr in convertFrom.Skip(1)) + convertFromPart += partStr; + } + + string[] convertTo = actions.First().ConvertTo.Split(" "); + if (convertTo.Length > 1) { + convertToCustomer = convertTo[0]; + foreach (string partStr in convertTo.Skip(1)) + convertToPart += partStr; + } + } } } @@ -165,9 +218,17 @@ actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Customer) && !string.IsNullOrWhiteSpace(mrbAction.PartNumber) && !string.IsNullOrWhiteSpace(mrbAction.LotNumber); + actionIsValid = actionIsValid && mrbAction.Quantity > 0; - if (mrbAction.Action.Equals("Scrap")) - return actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Justification); + if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) { + actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(convertFromCustomer) && + !string.IsNullOrWhiteSpace(convertFromPart) && + !string.IsNullOrWhiteSpace(convertToCustomer) && + !string.IsNullOrWhiteSpace(convertToPart); + } + + if (mrbAction.Action.Equals("Scrap", StringComparison.InvariantCultureIgnoreCase)) + actionIsValid = actionIsValid && !string.IsNullOrWhiteSpace(mrbAction.Justification); return actionIsValid; } @@ -177,6 +238,11 @@ try { if (!FormIsValid()) throw new Exception("You must complete the form before saving!"); + if (mrbAction.Action.Equals("Convert", StringComparison.InvariantCultureIgnoreCase)) { + mrbAction.ConvertFrom = $"{convertFromCustomer} {convertFromPart}"; + mrbAction.ConvertTo = $"{convertToCustomer} {convertToPart}"; + } + if (mrbAction.MRBNumber > 0) { if (mrbAction.ActionID <= 0) { await mrbService.CreateMRBAction(mrbAction); diff --git a/MesaFabApproval.Client/Pages/Components/MRBApproverSelector.razor b/MesaFabApproval.Client/Pages/Components/MRBApproverSelector.razor index 7951820..026bdf2 100644 --- a/MesaFabApproval.Client/Pages/Components/MRBApproverSelector.razor +++ b/MesaFabApproval.Client/Pages/Components/MRBApproverSelector.razor @@ -5,7 +5,7 @@ @if (availableApprovers is not null) { - + + + + + + + @if (DocNumberIsNA()) { + + } else { + + } + + + + + + @if ((DocNumberIsNA() && !string.IsNullOrWhiteSpace(document.Comment)) || + (!DocNumberIsNA() && ecnNoIsValid)) { + + @if (saveInProcess) { + + Processing + } else { + Save + } + + } + + Cancel + + + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public required PCR3Document document { get; set; } + + private string[] errors = { }; + + private bool complete = false; + + private bool saveInProcess = false; + + private bool ecnNoIsValid = true; + + protected override async Task OnParametersSetAsync() { + complete = document.CompletedByID > 0; + } + + private async Task Save() { + saveInProcess = true; + try { + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + return; + } + + if (!complete) { + document.CompletedByID = 0; + document.CompletedBy = null; + document.CompletedDate = DateTimeUtilities.MAX_DT; + } + + if (complete && document.CompletedByID <= 0) { + document.CompletedByID = authStateProvider.CurrentUser.UserID; + document.CompletedBy = authStateProvider.CurrentUser; + document.CompletedDate = DateTime.Now; + } + + if (!DocNumberIsNA() && !ecnNoIsValid) + throw new Exception($"{document.ECNNumber} is not a valid ECN#"); + if (DocNumberIsNA() && string.IsNullOrWhiteSpace(document.Comment)) + throw new Exception("you must provide a comment"); + + await pcrbService.UpdatePCR3Document(document); + + await pcrbService.GetPCR3DocumentsForPlanNumber(document.PlanNumber, true); + + saveInProcess = false; + + MudDialog.Close(DialogResult.Ok(document)); + } catch (Exception ex) { + saveInProcess = false; + snackbar.Add($"Unable to save document, because {ex.Message}", Severity.Error); + } + } + + private void Cancel() { + MudDialog.Close(DialogResult.Cancel()); + } + + private bool DocNumberIsNA() { + if (document.DocNumbers.ToLower().Equals("na") || + document.DocNumbers.ToLower().Equals("n/a")) { + return true; + } + return false; + } + + private async Task ECNNoIsValid(int ecnNumber) { + string? result = await ecnService.ECNNumberIsValidStr(ecnNumber); + if (result is null) ecnNoIsValid = true; + else ecnNoIsValid = false; + StateHasChanged(); + return result; + } +} diff --git a/MesaFabApproval.Client/Pages/Components/PCRBActionItemForm.razor b/MesaFabApproval.Client/Pages/Components/PCRBActionItemForm.razor new file mode 100644 index 0000000..72d5c59 --- /dev/null +++ b/MesaFabApproval.Client/Pages/Components/PCRBActionItemForm.razor @@ -0,0 +1,201 @@ +@inject MesaFabApprovalAuthStateProvider authStateProvider +@inject NavigationManager navigationManager +@inject IPCRBService pcrbService +@inject IUserService userService +@inject ISnackbar snackbar + + + + + + + + + @if (closedStatus) { + + } + + @foreach (User user in allActiveUsers.OrderBy(u => u.FirstName)) { + + } + + + + + + + @if (saveActionItemInProcess) { + + Processing + } else { + Submit + } + + + Cancel + + + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public int planNumber { get; set; } = 0; + + [Parameter] + public int step { get; set; } = 0; + + [Parameter] + public PCRBActionItem? actionItem { get; set; } = null; + + [Parameter] + public IEnumerable allActiveUsers { get; set; } + + private string[] errors = { }; + + private string name = ""; + private bool gating = false; + private DateTime? closedDate = DateTimeUtilities.MAX_DT; + private bool closedStatus = false; + private User? responsiblePerson = null; + + private bool saveActionItemInProcess = false; + + protected override async Task OnParametersSetAsync() { + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + } + + if (planNumber <= 0) { + snackbar.Add($"{planNumber} is not a valid PCRB plan#", Severity.Error); + MudDialog.Close(DialogResult.Cancel()); + } + + if (allActiveUsers is null || allActiveUsers.Count() <= 0) + allActiveUsers = await userService.GetAllActiveUsers(); + + if (actionItem is not null) { + name = actionItem.Name; + gating = actionItem.Gating; + closedStatus = actionItem.ClosedStatus; + closedDate = actionItem.ClosedDate; + if (closedDate.Equals(DateTimeUtilities.MAX_DT)) { + closedDate = DateTime.Now; + } + if (actionItem.ResponsiblePersonID > 0) { + if (actionItem.ResponsiblePerson is not null) responsiblePerson = actionItem.ResponsiblePerson; + else responsiblePerson = await userService.GetUserByUserId(actionItem.ResponsiblePersonID); + actionItem.ResponsiblePerson = responsiblePerson; + } + } else { + name = ""; + gating = false; + closedStatus = false; + closedDate = DateTime.Now; + responsiblePerson = null; + } + } + + private async Task Save() { + saveActionItemInProcess = true; + try { + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + } + + if (string.IsNullOrWhiteSpace(name)) throw new Exception("name missing"); + + if (actionItem is null) { + actionItem = new() { + Name = name, + UploadedBy = authStateProvider.CurrentUser, + UploadedByID = authStateProvider.CurrentUser.UserID, + PlanNumber = planNumber, + Step = step, + Gating = gating, + ClosedStatus = closedStatus, + ResponsiblePerson = responsiblePerson, + ResponsiblePersonID = responsiblePerson.UserID, + ClosedDate = closedDate, + ClosedBy = closedDate is null || closedDate >= DateTimeUtilities.MAX_DT ? null : authStateProvider.CurrentUser, + ClosedByID = closedStatus ? authStateProvider.CurrentUser.UserID : 0 + }; + + if (actionItem.ClosedStatus == false) { + actionItem.ClosedDate = DateTimeUtilities.MAX_DT; + } + + await pcrbService.CreateNewActionItem(actionItem); + } else { + actionItem.Name = name; + actionItem.Gating = gating; + actionItem.ClosedStatus = closedStatus; + actionItem.ClosedDate = closedDate; + if (closedStatus) { + actionItem.ClosedBy = authStateProvider.CurrentUser; + actionItem.ClosedByID = authStateProvider.CurrentUser.UserID; + } else { + actionItem.ClosedDate = DateTimeUtilities.MAX_DT; + } + actionItem.ResponsiblePerson = responsiblePerson; + if (responsiblePerson is not null) + actionItem.ResponsiblePersonID = responsiblePerson.UserID; + + await pcrbService.UpdateActionItem(actionItem); + } + + saveActionItemInProcess = false; + + MudDialog.Close(DialogResult.Ok(actionItem)); + } catch (Exception ex) { + saveActionItemInProcess = false; + snackbar.Add($"Unable to save action item, because {ex.Message}", Severity.Error); + } + } + + private void Cancel() { + MudDialog.Close(DialogResult.Cancel()); + } + + private Func UserToNameConverter = u => u is null ? string.Empty : u.GetFullName(); + + private Func AttachmentToFileNameConverter = a => a is null ? "" : a.FileName; +} diff --git a/MesaFabApproval.Client/Pages/Components/PCRBApproverForm.razor b/MesaFabApproval.Client/Pages/Components/PCRBApproverForm.razor new file mode 100644 index 0000000..5300779 --- /dev/null +++ b/MesaFabApproval.Client/Pages/Components/PCRBApproverForm.razor @@ -0,0 +1,250 @@ +@inject MesaFabApprovalAuthStateProvider authStateProvider +@inject NavigationManager navigationManager +@inject IPCRBService pcrbService +@inject IUserService userService +@inject IApprovalService approvalService +@inject ISnackbar snackbar + + + + + + + @foreach (string jt in availableJobTitles) { + @jt + } + + + @foreach (User user in availableUsers) { + @user.GetFullName() + } + + + + + + + @if (saveAttendeeInProcess) { + + Processing + } else { + Submit + } + + + Cancel + + + + +@code { + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public int planNumber { get; set; } = 0; + + [Parameter] + public int step { get; set; } = 0; + + [Parameter] + public Approval? approval { get; set; } = null; + + private HashSet availableJobTitles = new(); + + private Dictionary jobTitleToSubRoleMap = new(); + + private Dictionary> jobTitleToAvailableUsersMap = new(); + + private HashSet availableUsers = new(); + + private string[] errors = { }; + + private string selectedJobTitle = ""; + private User? selectedUser = null; + + private bool saveAttendeeInProcess = false; + + protected override async Task OnParametersSetAsync() { + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + } + + if (planNumber <= 0) { + snackbar.Add($"{planNumber} is not a valid PCRB plan#", Severity.Error); + MudDialog.Close(DialogResult.Cancel()); + } + + await GetAttendees(); + + if (approval is not null) { + selectedJobTitle = approval.RoleName; + if (approval.UserID > 0) { + if (approval.User is not null) { + selectedUser = approval.User; + } else { + selectedUser = await userService.GetUserByUserId(approval.UserID); + approval.User = selectedUser; + } + } + + SelectedJobTitleChanged(selectedJobTitle); + } else { + selectedJobTitle = ""; + selectedUser = null; + } + } + + private async Task Save() { + saveAttendeeInProcess = true; + try { + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + } + + if (string.IsNullOrWhiteSpace(selectedJobTitle)) throw new Exception("job title missing"); + if (selectedUser is null) throw new Exception("attendee not selected"); + + PCRB pcrb = await pcrbService.GetPCRBByPlanNumber(planNumber, false); + + if (approval is null) { + jobTitleToSubRoleMap.TryGetValue($"{selectedJobTitle}{selectedUser.UserID}", out SubRole? subRole); + + if (subRole is null) throw new Exception($"no approval role found for job title {selectedJobTitle}"); + + approval = new() { + RoleName = subRole.SubRoleCategoryItem, + SubRole = subRole.SubRoleName, + SubRoleID = subRole.SubRoleID, + IssueID = planNumber, + Step = step, + User = selectedUser, + UserID = selectedUser.UserID, + AssignedDate = DateTimeUtilities.MIN_DT + }; + + await approvalService.CreateApproval(approval); + } else { + int originalUserId = approval.UserID; + + approval.UserID = selectedUser.UserID; + approval.User = selectedUser; + + if (originalUserId != approval.UserID) { + if (approval.AssignedDate > DateTimeUtilities.MIN_DT) + approval.AssignedDate = DateTime.Now; + approval.NotifyDate = DateTimeUtilities.MIN_DT; + } + + await approvalService.UpdateApproval(approval); + + if (originalUserId != approval.UserID) + await pcrbService.NotifyNewApprovals(pcrb); + } + + saveAttendeeInProcess = false; + + MudDialog.Close(DialogResult.Ok(approval)); + } catch (Exception ex) { + saveAttendeeInProcess = false; + snackbar.Add($"Unable to save attendee, because {ex.Message}", Severity.Error); + } + } + + private void Cancel() { + MudDialog.Close(DialogResult.Cancel()); + } + + private Func UserToNameConverter = u => u is null ? string.Empty : u.GetFullName(); + + private async Task GetAttendees() { + int roleId = await approvalService.GetRoleIdForRoleName("Module Manager"); + + if (roleId <= 0) throw new Exception($"could not find Director role ID"); + + availableJobTitles.Clear(); + jobTitleToAvailableUsersMap.Clear(); + jobTitleToSubRoleMap.Clear(); + + IEnumerable subRoles = await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId); + + HashSet defaultSubRoleCategoryItems = new() { "Si Production", "Si Engineering", "Quality" }; + HashSet unusedSubRoleCategoryItems = new() { "GaN Engineering", "GaN Operations", "Integration" }; + foreach (SubRole subRole in subRoles) { + if (approval is null) { + if (!defaultSubRoleCategoryItems.Contains(subRole.SubRoleCategoryItem) && + !unusedSubRoleCategoryItems.Contains(subRole.SubRoleCategoryItem)) { + availableJobTitles.Add(subRole.SubRoleCategoryItem); + + IEnumerable subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + + jobTitleToAvailableUsersMap.Add(subRole.SubRoleCategoryItem, subRoleMembers); + + foreach (User member in subRoleMembers) { + jobTitleToSubRoleMap.Add($"{subRole.SubRoleCategoryItem}{member.UserID}", subRole); + } + } + } else { + if (!unusedSubRoleCategoryItems.Contains(subRole.SubRoleCategoryItem)) { + IEnumerable subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + + jobTitleToAvailableUsersMap.Add(subRole.SubRoleCategoryItem, subRoleMembers); + + foreach (User member in subRoleMembers) + availableUsers.Add(member); + } + } + } + } + + private void SelectedJobTitleChanged(string jobTitle) { + selectedJobTitle = jobTitle; + + selectedUser = null; + + availableUsers.Clear(); + + if (approval is null) { + if (jobTitleToAvailableUsersMap.TryGetValue(jobTitle, out IEnumerable? jobTitleMembers)) { + if (jobTitleMembers is not null) { + foreach (User member in jobTitleMembers) + availableUsers.Add(member); + } + } + } else { + foreach (IEnumerable memberList in jobTitleToAvailableUsersMap.Values) { + if (memberList is not null) { + foreach(User member in memberList) { + availableUsers.Add(member); + } + } + } + } + + StateHasChanged(); + } +} diff --git a/MesaFabApproval.Client/Pages/Components/PCRBAttachmentForm.razor b/MesaFabApproval.Client/Pages/Components/PCRBAttachmentForm.razor new file mode 100644 index 0000000..f0e97c7 --- /dev/null +++ b/MesaFabApproval.Client/Pages/Components/PCRBAttachmentForm.razor @@ -0,0 +1,140 @@ +@inject MesaFabApprovalAuthStateProvider authStateProvider +@inject NavigationManager navigationManager +@inject IPCRBService pcrbService +@inject ISnackbar snackbar + + + + + + + + + + @if (addFileInProcess) { + + Processing + } else { + Add File + } + + + + + + + + + @if (processing) { + + Processing + } + else { + Submit + } + + + Cancel + + + + +@code { + [CascadingParameter] MudDialogInstance MudDialog { get; set; } + + [Parameter] + public int planNumber { get; set; } = 0; + + [Parameter] + public int step { get; set; } = 0; + + private string[] errors = { }; + + private string fileName = ""; + + private IBrowserFile? file = null; + + private bool addFileInProcess = false; + private bool processing = false; + + protected override async Task OnParametersSetAsync() { + if (planNumber <= 0) { + snackbar.Add($"{planNumber} is not a valid PCRB plan#", Severity.Error); + MudDialog.Close(DialogResult.Cancel()); + } + + if (step <= 0) { + snackbar.Add($"{step} is not a valid PCRB stage#", Severity.Error); + MudDialog.Close(DialogResult.Cancel()); + } + + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + } + } + + private void AddFile(IBrowserFile newFile) { + addFileInProcess = true; + file = newFile; + fileName = newFile.Name; + addFileInProcess = false; + } + + private async Task Submit() { + processing = true; + try { + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + return; + } + + if (string.IsNullOrWhiteSpace(fileName)) + throw new Exception("file name missing"); + + if (file is null) + throw new Exception("file is missing"); + + PCRBAttachment attachment = new() { + Step = step, + UploadDateTime = DateTime.Now, + UploadedByID = authStateProvider.CurrentUser.UserID, + PlanNumber = planNumber, + File = file, + FileName = fileName + }; + + await pcrbService.UploadAttachment(attachment); + + processing = false; + + MudDialog.Close(DialogResult.Ok(attachment)); + } catch (Exception ex) { + snackbar.Add($"Unable to save document, because {ex.Message}", Severity.Error); + processing = false; + } + } + + private void Cancel() { + MudDialog.Close(DialogResult.Cancel()); + } +} diff --git a/MesaFabApproval.Client/Pages/Dashboard.razor b/MesaFabApproval.Client/Pages/Dashboard.razor index 4c88d96..6c831fb 100644 --- a/MesaFabApproval.Client/Pages/Dashboard.razor +++ b/MesaFabApproval.Client/Pages/Dashboard.razor @@ -7,6 +7,7 @@ @inject NavigationManager navigationManager @inject ISnackbar snackbar @inject IMRBService mrbService +@inject IPCRBService pcrbService @inject IECNService ecnService @inject ICAService caService @inject IJSRuntime jsRuntime @@ -88,6 +89,7 @@ } + + @if (stateProvider.CurrentUser is not null && myPCRBs is not null && !myPcrbsProcessing) { + + My PCRBs + + + + + + + + + + PCRB# + + + + + Title + + + + + Current Step + + + + + Submitted Date + + + + + Last Updated + + + + + Completed Date + + + + + + @context.PlanNumber + + @context.Title + @(GetCurrentPCRBStep(context.CurrentStep)) + @DateTimeUtilities.GetDateAsStringMinDefault(context.InsertTimeStamp) + @DateTimeUtilities.GetDateAsStringMinDefault(context.LastUpdateDate) + @DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate) + + + + + + + } else { + + Processing + + + } + @code { private IEnumerable approvalList = new List(); private IEnumerable myMRBs = new List(); + private IEnumerable myPCRBs = new List(); private IEnumerable ecnNumbers = new HashSet(); private IEnumerable caNumbers = new HashSet(); private IEnumerable mrbNumbers = new HashSet(); + private IEnumerable pcrbNumbers = new HashSet(); private bool myApprovalsProcessing = false; private bool myMrbsProcessing = false; + private bool myPcrbsProcessing = false; private string mrbSearchString = ""; + private string pcrbSearchString = ""; protected async override Task OnParametersSetAsync() { try { @@ -178,9 +259,17 @@ .ToList() .OrderByDescending(x => x.SubmittedDate); myMrbsProcessing = false; + + myPcrbsProcessing = true; + myPCRBs = (await pcrbService.GetAllPCRBs(false)).Where(p => p.OwnerID == stateProvider.CurrentUser.UserID) + .ToList() + .OrderByDescending(p => p.InsertTimeStamp); + myPcrbsProcessing = false; } } catch (Exception ex) { + myApprovalsProcessing = false; myMrbsProcessing = false; + myPcrbsProcessing = false; snackbar.Add($"Unable to load the dashboard, because {ex.Message}", Severity.Error); } } @@ -191,12 +280,15 @@ bool isEcn = false; bool isCa = false; bool isMrb = false; + bool isPcrb = false; if (ecnNumbers.Contains(issueId)) isEcn = true; if (caNumbers.Contains(issueId)) isCa = true; if (mrbNumbers.Contains(issueId)) isMrb = true; + if (pcrbNumbers.Contains(issueId)) + isPcrb = true; if (!isEcn && !isCa && !isMrb) { Task isEcnTask = ecnService.ECNNumberIsValid(issueId); @@ -208,6 +300,9 @@ Task isMrbTask = mrbService.NumberIsValid(issueId); tasks.Add(isMrbTask); + Task isPcrbTask = pcrbService.IdIsValid(issueId); + tasks.Add(isPcrbTask); + await Task.WhenAll(tasks); if (isEcnTask.Result) { @@ -216,12 +311,15 @@ isCa = true; } else if (isMrbTask.Result) { isMrb = true; + } else if (isPcrbTask.Result) { + isPcrb = true; } } if (isEcn) await GoToExternal($"{Configuration["OldFabApprovalUrl"]}/ECN/Edit?IssueID={issueId}", ""); if (isCa) await GoToExternal($"{Configuration["OldFabApprovalUrl"]}/CorrectiveAction/Edit?IssueID={issueId}", ""); if (isMrb) GoTo($"mrb/{issueId}"); + if (isPcrb) GoTo($"pcrb/{issueId}"); } private void GoTo(string page) { @@ -245,4 +343,21 @@ return true; return false; } + + private bool FilterFuncForPCRBTable(PCRB pcrb) => PCRBFilterFunc(pcrb, pcrbSearchString); + + private bool PCRBFilterFunc(PCRB pcrb, string searchString) { + if (string.IsNullOrWhiteSpace(searchString)) + return true; + if (pcrb.Title.ToLower().Contains(searchString.Trim().ToLower())) + return true; + if (pcrb.PlanNumber.ToString().Contains(searchString.Trim())) + return true; + return false; + } + + private string GetCurrentPCRBStep(int step) { + if (step < 0 || step > (PCRB.Stages.Length - 1)) return string.Empty; + else return PCRB.Stages[step]; + } } diff --git a/MesaFabApproval.Client/Pages/MRBSingle.razor b/MesaFabApproval.Client/Pages/MRBSingle.razor index 6f881b1..1af5ad3 100644 --- a/MesaFabApproval.Client/Pages/MRBSingle.razor +++ b/MesaFabApproval.Client/Pages/MRBSingle.razor @@ -75,6 +75,7 @@ @if (!mrbIsSubmitted && !string.IsNullOrWhiteSpace(mrb.Title)) { @if (saveInProcess) { @@ -87,6 +88,7 @@ @if (!mrbIsSubmitted && mrbNumberIsValid && (userIsOriginator || userIsAdmin)) { @if (deleteInProcess) { @@ -99,6 +101,7 @@ @if (!mrbIsSubmitted && mrbReadyToSubmit && (userIsOriginator || userIsAdmin)) { @if (submitInProcess) { @@ -111,6 +114,7 @@ @if (mrbIsSubmitted && userIsApprover && approvalsArePending) { @if (approvalInProcess) { @@ -121,6 +125,7 @@ @if (denialInProcess) { @@ -133,6 +138,7 @@ @if ((userIsOriginator || userIsAdmin) && mrb.StageNo > 0 && mrb.StageNo < 4) { @if (recallInProcess) { @@ -339,20 +345,18 @@ Clearable Immediate Label="Total Quantity" /> - + @if (attachmentUploadInProcess) { @@ -478,6 +483,7 @@ @if (deleteActionInProcess) { @@ -513,6 +519,7 @@ Color="Color.Tertiary" Class="flex-grow-0" Style="max-width: 185px;" + Disabled="@completeAllActionsInProcess" OnClick="@((e) => CompleteAllActions())"> @if (completeAllActionsInProcess) { @@ -531,6 +538,11 @@ SortLabel="Sort By" Hover="true"> + + Download as CSV File + @if (deleteActionInProcess) { @@ -682,6 +695,7 @@ @if (submitInProcess) { @@ -820,28 +834,107 @@ } private async void SaveMRB() { - try { - saveInProcess = true; - MRB initialMrb = new MRB() { - MRBNumber = mrb.MRBNumber, - Status = mrb.Status, - StageNo = 0, - NumberOfLotsAffected = 0 - }; - if (mrb is not null) { + if (!saveInProcess) { + try { + saveInProcess = true; + MRB initialMrb = new MRB() { + MRBNumber = mrb.MRBNumber, + Status = mrb.Status, + StageNo = 0, + NumberOfLotsAffected = 0 + }; + if (mrb is not null) { + User? originator = allActiveUsers.Where(u => $"{u.FirstName} {u.LastName}".Equals(mrb.OriginatorName)).FirstOrDefault(); + if (originator is not null) mrb.OriginatorID = originator.UserID; + + if (mrb.MRBNumber <= 0) { + await mrbService.CreateNewMRB(mrb); + mrb = await mrbService.GetMRBByTitle(mrb.Title, true); + cache.Set("redirectUrl", $"mrb/{mrb.MRBNumber}"); + } + + mrb.NumberOfLotsAffected = 0; + foreach (MRBAction action in mrbActions) { + if (action is not null) { + action.MRBNumber = mrbNumberInt; + if (action.ActionID > 0) { + await mrbService.UpdateMRBAction(action); + } else { + await mrbService.CreateMRBAction(action); + } + + mrb.NumberOfLotsAffected += action.Quantity; + } + } + + if (mrb.MRBNumber > 0) await mrbService.UpdateMRB(mrb); + + mrbNumber = mrb.MRBNumber.ToString(); + mrbNumberInt = mrb.MRBNumber; + + mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true); + } + saveInProcess = false; + StateHasChanged(); + snackbar.Add($"MRB {mrb.MRBNumber} successfully saved", Severity.Success); + + if (initialMrb.MRBNumber <= 0) + navigationManager.NavigateTo($"mrb/{mrb.MRBNumber}"); + } catch (Exception ex) { + saveInProcess = false; + snackbar.Add(ex.Message, Severity.Error); + } + saveInProcess = false; + } + } + + private async void DeleteMRB() { + if (!deleteInProcess) { + try { + bool? result = await dialogService.ShowMessageBox( + "Warning", + $"Are you sure you want to delete MRB# {mrb.MRBNumber}?", + yesText: "Yes", noText: "No" + ); + + if (result == true) { + deleteInProcess = true; + + await mrbService.DeleteMRB(mrb.MRBNumber); + + deleteInProcess = false; + + snackbar.Add("MRB successfully deleted", Severity.Success); + + navigationManager.NavigateTo("mrb/all"); + } + } catch (Exception ex) { + deleteInProcess = false; + + snackbar.Add($"Unable to delete MRB, because {ex.Message}", Severity.Error); + } + } + } + + private async void SubmitMRBForApproval() { + if (!submitInProcess && !processing) { + submitInProcess = true; + processing = true; + try { + if (mrb is null) throw new Exception("MRB cannot be null"); + User? originator = allActiveUsers.Where(u => $"{u.FirstName} {u.LastName}".Equals(mrb.OriginatorName)).FirstOrDefault(); if (originator is not null) mrb.OriginatorID = originator.UserID; - if (mrb.MRBNumber <= 0) { - await mrbService.CreateNewMRB(mrb); - mrb = await mrbService.GetMRBByTitle(mrb.Title, true); - cache.Set("redirectUrl", $"mrb/{mrb.MRBNumber}"); + if (mrb.StageNo == 0) { + mrb.StageNo++; + mrb.SubmittedDate = DateTime.Now; } mrb.NumberOfLotsAffected = 0; foreach (MRBAction action in mrbActions) { if (action is not null) { - action.MRBNumber = mrbNumberInt; + action.MRBNumber = mrb.MRBNumber; if (action.ActionID > 0) { await mrbService.UpdateMRBAction(action); } else { @@ -852,189 +945,18 @@ } } - if (mrb.MRBNumber > 0) await mrbService.UpdateMRB(mrb); - - mrbNumber = mrb.MRBNumber.ToString(); - mrbNumberInt = mrb.MRBNumber; - mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true); - } - saveInProcess = false; - StateHasChanged(); - snackbar.Add($"MRB {mrb.MRBNumber} successfully saved", Severity.Success); - if (initialMrb.MRBNumber <= 0) - navigationManager.NavigateTo($"mrb/{mrb.MRBNumber}"); - } catch (Exception ex) { - saveInProcess = false; - snackbar.Add(ex.Message, Severity.Error); - } - saveInProcess = false; - } - - private async void DeleteMRB() { - try { - bool? result = await dialogService.ShowMessageBox( - "Warning", - $"Are you sure you want to delete MRB# {mrb.MRBNumber}?", - yesText: "Yes", noText: "No" - ); - - if (result == true) { - deleteInProcess = true; - - await mrbService.DeleteMRB(mrb.MRBNumber); - - deleteInProcess = false; - - snackbar.Add("MRB successfully deleted", Severity.Success); - - navigationManager.NavigateTo("mrb/all"); - } - } catch (Exception ex) { - deleteInProcess = false; - - snackbar.Add($"Unable to delete MRB, because {ex.Message}", Severity.Error); - } - } - - private async void SubmitMRBForApproval() { - submitInProcess = true; - processing = true; - try { - if (mrb is null) throw new Exception("MRB cannot be null"); - - User? originator = allActiveUsers.Where(u => $"{u.FirstName} {u.LastName}".Equals(mrb.OriginatorName)).FirstOrDefault(); - if (originator is not null) mrb.OriginatorID = originator.UserID; - - if (mrb.StageNo == 0) { - mrb.StageNo++; - mrb.SubmittedDate = DateTime.Now; - } - - mrb.NumberOfLotsAffected = 0; - foreach (MRBAction action in mrbActions) { - if (action is not null) { - action.MRBNumber = mrb.MRBNumber; - if (action.ActionID > 0) { - await mrbService.UpdateMRBAction(action); - } else { - await mrbService.CreateMRBAction(action); - } - - mrb.NumberOfLotsAffected += action.Quantity; - } - } - - mrbActions = await mrbService.GetMRBActionsForMRB(mrbNumberInt, true); - - await mrbService.UpdateMRB(mrb); - - await mrbService.SubmitForApproval(mrb); - - await mrbService.NotifyNewApprovals(mrb); - - if (mrb.StageNo == 1) { - StringBuilder messageBuilder = new(); - messageBuilder.Append($"MRB# {mrb.MRBNumber} [{mrb.Title}] has been submitted for approval. "); - messageBuilder.Append("You will receive an email after it has been approved."); - - MRBNotification notification = new() { - MRB = mrb, - Message = messageBuilder.ToString() - }; - - await mrbService.NotifyOriginator(notification); - } - - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - - nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); - taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); - - submitInProcess = false; - processing = false; - - snackbar.Add("MRB submitted for approval", Severity.Success); - } catch (Exception ex) { - submitInProcess = false; - processing = false; - snackbar.Add($"Unable to submit MRB for approval, because {ex.Message}", Severity.Error); - } - - StateHasChanged(); - } - - private async void ApproveMRB() { - approvalInProcess = true; - processing = true; - try { - if (mrb is null) throw new Exception("MRB is null"); - - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - - if (mrbApprovals is null || mrbApprovals.Count() <= 0) - throw new Exception("there are no approvals to approve"); - if (authStateProvider.CurrentUser is null) { - navigationManager.NavigateTo("login"); - return; - } - - string? comments = ""; - - DialogParameters parameters = new DialogParameters { { x => x.comments, comments } }; - var dialog = dialogService.Show($"Approval Comments", parameters); - - var result = await dialog.Result; - - if (result.Canceled) throw new Exception("you must provide approval comments"); - - comments = result.Data.ToString(); - - if (result.Canceled) throw new Exception("you must provide your approval comments"); - - IEnumerable approvals = mrbApprovals.Where(a => a.UserID == authStateProvider.CurrentUser.UserID && - a.ItemStatus == 0 && - !(a.CompletedDate < DateTimeUtilities.MAX_DT) && - a.Step == mrb.StageNo); - - foreach (Approval approval in approvals) { - approval.CompletedDate = DateTime.Now; - approval.Comments = comments is null ? "" : comments; - approval.ItemStatus = 1; - await approvalService.Approve(approval); - - IEnumerable sameRoleApprovals = mrbApprovals.Where(a => a.Step == mrb.StageNo && - !(a.UserID == authStateProvider.CurrentUser.UserID) && - a.ItemStatus == 0 && - a.SubRoleCategoryItem.ToLower().Equals(approval.SubRoleCategoryItem.ToLower()) && - !(a.CompletedDate < DateTimeUtilities.MAX_DT)); - foreach (Approval sameApp in sameRoleApprovals) { - sameApp.CompletedDate = DateTime.Now; - sameApp.Comments = comments is null ? "" : comments; - sameApp.ItemStatus = 1; - await approvalService.Approve(sameApp); - } - } - - IEnumerable remainingApprovalsInKind = mrbApprovals.Where(a => a.Step == mrb.StageNo && - a.ItemStatus == 0 && - !(a.CompletedDate < DateTimeUtilities.MAX_DT)); - - if (remainingApprovalsInKind is null || remainingApprovalsInKind.Count() <= 0) { - mrb.StageNo++; - if (mrb.StageNo == 3) mrb.ApprovalDate = DateTime.Now; await mrbService.UpdateMRB(mrb); - if (mrb.StageNo < 3) - SubmitMRBForApproval(); + await mrbService.SubmitForApproval(mrb); - if (mrb.StageNo == 3) { - GenerateActionTasks(); + await mrbService.NotifyNewApprovals(mrb); + if (mrb.StageNo == 1) { StringBuilder messageBuilder = new(); - messageBuilder.Append($"MRB# {mrb.MRBNumber} [{mrb.Title}] has been approved. "); - messageBuilder.Append("You will receive an email when all actions are complete."); + messageBuilder.Append($"MRB# {mrb.MRBNumber} [{mrb.Title}] has been submitted for approval. "); + messageBuilder.Append("You will receive an email after it has been approved."); MRBNotification notification = new() { MRB = mrb, @@ -1042,151 +964,253 @@ }; await mrbService.NotifyOriginator(notification); - - string msg = $"MRB# {mrb.MRBNumber} [{mrb.Title}] has been approved."; - - notification = new() { - MRB = mrb, - Message = msg - }; - - await mrbService.NotifyQAPreApprover(notification); } + + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + + nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); + taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); + + submitInProcess = false; + processing = false; + + snackbar.Add("MRB submitted for approval", Severity.Success); + } catch (Exception ex) { + submitInProcess = false; + processing = false; + snackbar.Add($"Unable to submit MRB for approval, because {ex.Message}", Severity.Error); } - mrbActions = await mrbService.GetMRBActionsForMRB(mrb.MRBNumber, true); - - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); - nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); - - if (mrb.StageNo == 3 && taskApprovals.Count() <= 0) { - mrb.StageNo++; - mrb.CloseDate = DateTime.Now; - await mrbService.UpdateMRB(mrb); - - string body = $"MRB# {mrb.MRBNumber} [{mrb.Title}] is complete."; - - MRBNotification notification = new() { - MRB = mrb, - Message = body - }; - - await mrbService.NotifyOriginator(notification); - await mrbService.NotifyQAPreApprover(notification); - - snackbar.Add("MRB complete", Severity.Success); - } - - approvalInProcess = false; - processing = false; - StateHasChanged(); + } + } - snackbar.Add("Successfully approved", Severity.Success); - } catch (Exception ex) { - approvalInProcess = false; - processing = false; + private async void ApproveMRB() { + if (!approvalInProcess && !processing) { + approvalInProcess = true; + processing = true; + try { + if (mrb is null) throw new Exception("MRB is null"); - snackbar.Add($"Unable to approve, because {ex.Message}", Severity.Error); + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + + if (mrbApprovals is null || mrbApprovals.Count() <= 0) + throw new Exception("there are no approvals to approve"); + if (authStateProvider.CurrentUser is null) { + navigationManager.NavigateTo("login"); + return; + } + + string? comments = ""; + + DialogParameters parameters = new DialogParameters { { x => x.comments, comments } }; + var dialog = dialogService.Show($"Approval Comments", parameters); + + var result = await dialog.Result; + + if (result.Canceled) throw new Exception("you must provide approval comments"); + + comments = result.Data.ToString(); + + IEnumerable approvals = mrbApprovals.Where(a => a.UserID == authStateProvider.CurrentUser.UserID && + a.ItemStatus == 0 && + !(a.CompletedDate < DateTimeUtilities.MAX_DT) && + a.Step == mrb.StageNo); + + foreach (Approval approval in approvals) { + approval.CompletedDate = DateTime.Now; + approval.Comments = comments is null ? "" : comments; + approval.ItemStatus = 1; + await approvalService.Approve(approval); + + IEnumerable sameRoleApprovals = mrbApprovals.Where(a => a.Step == mrb.StageNo && + !(a.UserID == authStateProvider.CurrentUser.UserID) && + a.ItemStatus == 0 && + a.SubRoleCategoryItem.ToLower().Equals(approval.SubRoleCategoryItem.ToLower()) && + !(a.CompletedDate < DateTimeUtilities.MAX_DT)); + foreach (Approval sameApp in sameRoleApprovals) { + sameApp.CompletedDate = DateTime.Now; + sameApp.Comments = comments is null ? "" : comments; + sameApp.ItemStatus = 1; + await approvalService.Approve(sameApp); + } + } + + IEnumerable remainingApprovalsInKind = mrbApprovals.Where(a => a.Step == mrb.StageNo && + a.ItemStatus == 0 && + !(a.CompletedDate < DateTimeUtilities.MAX_DT)); + + if (remainingApprovalsInKind is null || remainingApprovalsInKind.Count() <= 0) { + mrb.StageNo++; + if (mrb.StageNo == 3) mrb.ApprovalDate = DateTime.Now; + await mrbService.UpdateMRB(mrb); + + if (mrb.StageNo < 3) + SubmitMRBForApproval(); + + if (mrb.StageNo == 3) { + GenerateActionTasks(); + + StringBuilder messageBuilder = new(); + messageBuilder.Append($"MRB# {mrb.MRBNumber} [{mrb.Title}] has been approved. "); + messageBuilder.Append("You will receive an email when all actions are complete."); + + MRBNotification notification = new() { + MRB = mrb, + Message = messageBuilder.ToString() + }; + + await mrbService.NotifyOriginator(notification); + + string msg = $"MRB# {mrb.MRBNumber} [{mrb.Title}] has been approved."; + + notification = new() { + MRB = mrb, + Message = msg + }; + + await mrbService.NotifyQAPreApprover(notification); + } + } + + mrbActions = await mrbService.GetMRBActionsForMRB(mrb.MRBNumber, true); + + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); + nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); + + if (mrb.StageNo == 3 && taskApprovals.Count() <= 0) { + mrb.StageNo++; + mrb.CloseDate = DateTime.Now; + await mrbService.UpdateMRB(mrb); + + string body = $"MRB# {mrb.MRBNumber} [{mrb.Title}] is complete."; + + MRBNotification notification = new() { + MRB = mrb, + Message = body + }; + + await mrbService.NotifyOriginator(notification); + await mrbService.NotifyQAPreApprover(notification); + + snackbar.Add("MRB complete", Severity.Success); + } + + approvalInProcess = false; + processing = false; + + StateHasChanged(); + + snackbar.Add("Successfully approved", Severity.Success); + } catch (Exception ex) { + approvalInProcess = false; + processing = false; + + snackbar.Add($"Unable to approve, because {ex.Message}", Severity.Error); + } } } private async void RecallMRB() { - recallInProcess = true; - try { - if (mrb is null) - throw new Exception("MRB cannot be null"); + if (!recallInProcess) { + recallInProcess = true; + try { + if (mrb is null) + throw new Exception("MRB cannot be null"); + + User? currentUser = authStateProvider.CurrentUser; + if (currentUser is null) { + recallInProcess = false; + snackbar.Add("You must be logged in to recall this MRB", Severity.Error); + navigationManager.NavigateTo($"login/mrb/{mrb.MRBNumber}"); + } else { + await mrbService.RecallMRB(mrb, currentUser); + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); + taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); + } - User? currentUser = authStateProvider.CurrentUser; - if (currentUser is null) { recallInProcess = false; - snackbar.Add("You must be logged in to recall this MRB", Severity.Error); - navigationManager.NavigateTo($"login/mrb/{mrb.MRBNumber}"); - } else { - await mrbService.RecallMRB(mrb, currentUser); - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); - taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); + + StateHasChanged(); + + snackbar.Add("MRB successfully recalled", Severity.Success); + } catch (Exception ex) { + recallInProcess = false; + snackbar.Add($"Unable to recall MRB, because {ex.Message}", Severity.Error); + throw; } - - recallInProcess = false; - - StateHasChanged(); - - snackbar.Add("MRB successfully recalled", Severity.Success); - } catch (Exception ex) { - recallInProcess = false; - snackbar.Add($"Unable to recall MRB, because {ex.Message}", Severity.Error); - throw; } } private async void DenyMRB() { - denialInProcess = true; - try { - if (mrbApprovals is null || mrbApprovals.Count() <= 0) - throw new Exception("there are no approvals to deny"); - if (authStateProvider.CurrentUser is null) { - navigationManager.NavigateTo("login"); - return; + if (!denialInProcess) { + denialInProcess = true; + try { + if (mrbApprovals is null || mrbApprovals.Count() <= 0) + throw new Exception("there are no approvals to deny"); + if (authStateProvider.CurrentUser is null) { + navigationManager.NavigateTo("login"); + return; + } + if (mrb is null) throw new Exception("MRB is null"); + + string? comments = ""; + + DialogParameters parameters = new DialogParameters { { x => x.comments, comments } }; + var dialog = dialogService.Show($"Denial Comments", parameters); + + var result = await dialog.Result; + + if (result.Canceled) throw new Exception("you must provide your denial comments"); + + comments = result.Data.ToString(); + + if (string.IsNullOrWhiteSpace(comments)) throw new Exception("you must provide your denial comments"); + + IEnumerable approvals = mrbApprovals.Where(a => !(a.CompletedDate < DateTimeUtilities.MAX_DT) && + a.Step == mrb.StageNo); + + foreach (Approval approval in approvals) { + approval.CompletedDate = DateTime.Now; + approval.Comments = comments is null ? "" : comments; + approval.ItemStatus = -1; + await approvalService.Deny(approval); + } + + mrb.StageNo = 0; + mrb.SubmittedDate = DateTimeUtilities.MIN_DT; + + await mrbService.UpdateMRB(mrb); + + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); + + StringBuilder messageBuilder = new(); + messageBuilder.AppendLine($"MRB# {mrb.MRBNumber} [{mrb.Title}] has been denied by {authStateProvider.CurrentUser.GetFullName()}. "); + messageBuilder.AppendLine(""); + messageBuilder.Append($"Comments: {comments}"); + + MRBNotification notification = new() { + MRB = mrb, + Message = messageBuilder.ToString() + }; + + await mrbService.NotifyOriginator(notification); + + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + + denialInProcess = false; + + StateHasChanged(); + + snackbar.Add("Successfully denied", Severity.Success); + } catch (Exception ex) { + denialInProcess = false; + + snackbar.Add($"Unable to process denial, because {ex.Message}", Severity.Error); } - if (mrb is null) throw new Exception("MRB is null"); - - string? comments = ""; - - DialogParameters parameters = new DialogParameters { { x => x.comments, comments } }; - var dialog = dialogService.Show($"Denial Comments", parameters); - - var result = await dialog.Result; - - if (result.Canceled) throw new Exception("you must provide your denial comments"); - - comments = result.Data.ToString(); - - if (string.IsNullOrWhiteSpace(comments)) throw new Exception("you must provide your denial comments"); - - IEnumerable approvals = mrbApprovals.Where(a => !(a.CompletedDate < DateTimeUtilities.MAX_DT) && - a.Step == mrb.StageNo); - - foreach (Approval approval in approvals) { - approval.CompletedDate = DateTime.Now; - approval.Comments = comments is null ? "" : comments; - approval.ItemStatus = -1; - await approvalService.Deny(approval); - } - - mrb.StageNo = 0; - mrb.SubmittedDate = DateTimeUtilities.MIN_DT; - - await mrbService.UpdateMRB(mrb); - - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - nonTaskApprovals = mrbApprovals.Where(a => a.Step < 3).ToList(); - - StringBuilder messageBuilder = new(); - messageBuilder.AppendLine($"MRB# {mrb.MRBNumber} [{mrb.Title}] has been denied by {authStateProvider.CurrentUser.GetFullName()}. "); - messageBuilder.AppendLine(""); - messageBuilder.Append($"Comments: {comments}"); - - MRBNotification notification = new() { - MRB = mrb, - Message = messageBuilder.ToString() - }; - - await mrbService.NotifyOriginator(notification); - - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - - denialInProcess = false; - - StateHasChanged(); - - snackbar.Add("Successfully denied", Severity.Success); - } catch (Exception ex) { - denialInProcess = false; - - snackbar.Add($"Unable to process denial, because {ex.Message}", Severity.Error); } } @@ -1280,82 +1304,85 @@ } private async void CompleteAllActions() { - try { - completeAllActionsInProcess = true; - if (mrbActions is null) throw new Exception("MRB actions cannot be null"); - if (authStateProvider.CurrentUser is null) - throw new Exception("you must be logged in to complete this action"); - if (mrb is null) throw new Exception("MRB cannot be null"); + if (!completeAllActionsInProcess) { + try { + completeAllActionsInProcess = true; - string? comments = ""; + if (mrbActions is null) throw new Exception("MRB actions cannot be null"); + if (authStateProvider.CurrentUser is null) + throw new Exception("you must be logged in to complete this action"); + if (mrb is null) throw new Exception("MRB cannot be null"); - MRBAction? firstAction = mrbActions.FirstOrDefault(); + string? comments = ""; - if (firstAction is null) throw new Exception("there are actions to complete"); + MRBAction? firstAction = mrbActions.FirstOrDefault(); - DialogParameters parameters = new DialogParameters { + if (firstAction is null) throw new Exception("there are actions to complete"); + + DialogParameters parameters = new DialogParameters { { x => x.comments, comments }, { x => x.actionId, firstAction.ActionID} }; - var dialog = dialogService.Show($"Completion Comments", parameters); + var dialog = dialogService.Show($"Completion Comments", parameters); - var result = await dialog.Result; + var result = await dialog.Result; - if (result.Canceled) - throw new Exception("you must provide your completion comments"); + if (result.Canceled) + throw new Exception("you must provide your completion comments"); - comments = result.Data.ToString(); + comments = result.Data.ToString(); - if (string.IsNullOrWhiteSpace(comments)) - throw new Exception("you must provide your completion comments"); + if (string.IsNullOrWhiteSpace(comments)) + throw new Exception("you must provide your completion comments"); - foreach (MRBAction action in mrbActions) { - action.CompletedDate = DateTime.Now; - action.CompletedByUserID = authStateProvider.CurrentUser.UserID; - action.CompletedByUser = authStateProvider.CurrentUser; + foreach (MRBAction action in mrbActions) { + action.CompletedDate = DateTime.Now; + action.CompletedByUserID = authStateProvider.CurrentUser.UserID; + action.CompletedByUser = authStateProvider.CurrentUser; - await mrbService.UpdateMRBAction(action); + await mrbService.UpdateMRBAction(action); - mrbActions = await mrbService.GetMRBActionsForMRB(action.MRBNumber, true); + mrbActions = await mrbService.GetMRBActionsForMRB(action.MRBNumber, true); - mrbActionAttachments = await mrbService.GetAllActionAttachmentsForMRB(action.MRBNumber, true); - } - - foreach (Approval approval in taskApprovals) { - if (approval.ItemStatus == 0) { - approval.Comments = comments; - await approvalService.Approve(approval); + mrbActionAttachments = await mrbService.GetAllActionAttachmentsForMRB(action.MRBNumber, true); } + + foreach (Approval approval in taskApprovals) { + if (approval.ItemStatus == 0) { + approval.Comments = comments; + await approvalService.Approve(approval); + } + } + + mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); + taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); + + int outStandingTaskCount = taskApprovals.Where(a => a.CompletedDate >= DateTimeUtilities.MAX_DT).Count(); + + if (outStandingTaskCount == 0) { + mrb.StageNo++; + mrb.CloseDate = DateTime.Now; + await mrbService.UpdateMRB(mrb); + + string body = $"MRB# {mrb.MRBNumber} [{mrb.Title}] is complete."; + + MRBNotification notification = new() { + MRB = mrb, + Message = body + }; + + await mrbService.NotifyOriginator(notification); + await mrbService.NotifyQAPreApprover(notification); + + snackbar.Add("All MRB actions complete", Severity.Success); + } + + completeAllActionsInProcess = false; + + StateHasChanged(); + } catch (Exception ex) { + completeAllActionsInProcess = false; + snackbar.Add($"Unable to mark action complete, because {ex.Message}", Severity.Error); } - - mrbApprovals = await approvalService.GetApprovalsForIssueId(mrb.MRBNumber, true); - taskApprovals = mrbApprovals.Where(a => a.Step >= 3).ToList(); - - int outStandingTaskCount = taskApprovals.Where(a => a.CompletedDate >= DateTimeUtilities.MAX_DT).Count(); - - if (outStandingTaskCount == 0) { - mrb.StageNo++; - mrb.CloseDate = DateTime.Now; - await mrbService.UpdateMRB(mrb); - - string body = $"MRB# {mrb.MRBNumber} [{mrb.Title}] is complete."; - - MRBNotification notification = new() { - MRB = mrb, - Message = body - }; - - await mrbService.NotifyOriginator(notification); - await mrbService.NotifyQAPreApprover(notification); - - snackbar.Add("All MRB actions complete", Severity.Success); - } - - completeAllActionsInProcess = false; - - StateHasChanged(); - } catch (Exception ex) { - completeAllActionsInProcess = false; - snackbar.Add($"Unable to mark action complete, because {ex.Message}", Severity.Error); } } @@ -1376,6 +1403,7 @@ if (mrb is not null && !mrb.Category.Equals("Material Transfer")) { isReadyToSubmit = isReadyToSubmit && mrbActions is not null && mrbActions.Count() > 0; + isReadyToSubmit = isReadyToSubmit && mrb.NumberOfLotsAffected > 0; } isReadyToSubmit = isReadyToSubmit && mrbAttachments is not null && mrbAttachments.Count() > 0; @@ -1478,23 +1506,25 @@ } private async void DeleteAction(MRBAction mrbAction) { - deleteActionInProcess = true; - try { - if (mrbAction is null) - throw new ArgumentNullException("Action cannot be null"); + if (!deleteActionInProcess) { + deleteActionInProcess = true; + try { + if (mrbAction is null) + throw new ArgumentNullException("Action cannot be null"); - await mrbService.DeleteMRBAction(mrbAction); + await mrbService.DeleteMRBAction(mrbAction); - List mrbActionList = mrbActions.ToList(); - mrbActionList.RemoveAll(x => x.ActionID == mrbAction.ActionID); - mrbActions = mrbActionList; + List mrbActionList = mrbActions.ToList(); + mrbActionList.RemoveAll(x => x.ActionID == mrbAction.ActionID); + mrbActions = mrbActionList; - snackbar.Add("Action successfully deleted", Severity.Success); - } catch (Exception ex) { - snackbar.Add(ex.Message, Severity.Error); + snackbar.Add("Action successfully deleted", Severity.Success); + } catch (Exception ex) { + snackbar.Add(ex.Message, Severity.Error); + } + deleteActionInProcess = false; + StateHasChanged(); } - deleteActionInProcess = false; - StateHasChanged(); } private bool FilterFuncForMRBActionTable(MRBAction action) => MRBActionFilterFunc(action, actionSearchString); @@ -1517,43 +1547,47 @@ } private async Task AddAttachments(InputFileChangeEventArgs args) { - attachmentUploadInProcess = true; - try { - IReadOnlyList attachments = args.GetMultipleFiles(); + if (!attachmentUploadInProcess) { + attachmentUploadInProcess = true; + try { + IReadOnlyList attachments = args.GetMultipleFiles(); - if (authStateProvider.CurrentUser is not null) { - await mrbService.UploadAttachments(attachments, mrbNumberInt); + if (authStateProvider.CurrentUser is not null) { + await mrbService.UploadAttachments(attachments, mrbNumberInt); - mrbAttachments = await mrbService.GetAllAttachmentsForMRB(mrbNumberInt, true); + mrbAttachments = await mrbService.GetAllAttachmentsForMRB(mrbNumberInt, true); + attachmentUploadInProcess = false; + + StateHasChanged(); + } + } catch (Exception ex) { attachmentUploadInProcess = false; - - StateHasChanged(); + snackbar.Add($"Unable to upload attachments, because {ex.Message}", Severity.Error); } - } catch (Exception ex) { - attachmentUploadInProcess = false; - snackbar.Add($"Unable to upload attachments, because {ex.Message}", Severity.Error); } } private async void DeleteAttachment(MRBAttachment mrbAttachment) { - deleteAttachmentInProcess = true; - try { - if (mrbAttachment is null) - throw new ArgumentNullException("Attachment cannot be null"); + if (!deleteAttachmentInProcess) { + deleteAttachmentInProcess = true; + try { + if (mrbAttachment is null) + throw new ArgumentNullException("Attachment cannot be null"); - await mrbService.DeleteAttachment(mrbAttachment); + await mrbService.DeleteAttachment(mrbAttachment); - List mrbAttachmentList = mrbAttachments.ToList(); - mrbAttachmentList.RemoveAll(x => x.AttachmentID == mrbAttachment.AttachmentID); - mrbAttachments = mrbAttachmentList; + List mrbAttachmentList = mrbAttachments.ToList(); + mrbAttachmentList.RemoveAll(x => x.AttachmentID == mrbAttachment.AttachmentID); + mrbAttachments = mrbAttachmentList; - snackbar.Add("Attachment successfully deleted", Severity.Success); - } catch (Exception ex) { - snackbar.Add(ex.Message, Severity.Error); + snackbar.Add("Attachment successfully deleted", Severity.Success); + } catch (Exception ex) { + snackbar.Add(ex.Message, Severity.Error); + } + deleteAttachmentInProcess = false; + StateHasChanged(); } - deleteAttachmentInProcess = false; - StateHasChanged(); } private async Task SpecsImpactedChanged(bool newValue) { diff --git a/MesaFabApproval.Client/Pages/PCRBAll.razor b/MesaFabApproval.Client/Pages/PCRBAll.razor index 30088d0..d01da3e 100644 --- a/MesaFabApproval.Client/Pages/PCRBAll.razor +++ b/MesaFabApproval.Client/Pages/PCRBAll.razor @@ -30,7 +30,7 @@ - Plan# + Change# @@ -43,6 +43,7 @@ Owner + Stage Submitted Date @@ -65,6 +66,7 @@ @context.Title @context.OwnerName + @(GetStageName(context.CurrentStep)) @DateTimeUtilities.GetDateAsStringMinDefault(context.InsertTimeStamp) @DateTimeUtilities.GetDateAsStringMinDefault(context.LastUpdateDate) @DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate) @@ -116,4 +118,9 @@ cache.Set("redirectUrl", page); navigationManager.NavigateTo(page); } + + private string GetStageName(int step) { + if (step >= PCRB.Stages.Length || step < 0) return ""; + return PCRB.Stages[step]; + } } diff --git a/MesaFabApproval.Client/Pages/PCRBSingle.razor b/MesaFabApproval.Client/Pages/PCRBSingle.razor index 7dfd11d..a561455 100644 --- a/MesaFabApproval.Client/Pages/PCRBSingle.razor +++ b/MesaFabApproval.Client/Pages/PCRBSingle.razor @@ -6,6 +6,9 @@ @inject IPCRBService pcrbService @inject IUserService userService @inject IMemoryCache cache +@inject IConfiguration config +@inject IDialogService dialogService +@inject IApprovalService approvalService @inject NavigationManager navigationManager @inject MesaFabApprovalAuthStateProvider authStateProvider @@ -42,26 +45,45 @@ } - bool pcrbIsSubmitted = pcrb.InsertTimeStamp > DateTimeUtilities.MIN_DT; + bool pcrbIsSubmitted = pcrb.CurrentStep > 0 && pcrb.InsertTimeStamp > DateTimeUtilities.MIN_DT; + bool pcrbIsComplete = pcrb.ClosedDate < DateTimeUtilities.MAX_DT && pcrb.CurrentStep == (PCRB.Stages.Length - 1); bool userIsOriginator = pcrb.OwnerID == authStateProvider.CurrentUser?.UserID; bool userIsAdmin = authStateProvider.CurrentUser is null ? false : authStateProvider.CurrentUser.IsAdmin; + bool userIsApprover = UserIsApprover(); - - @if (!pcrbIsSubmitted && !string.IsNullOrWhiteSpace(pcrb.Title)) { - - @if (saveInProcess) { - - Processing - } else { - Save - } - - } - + @if ((!pcrbIsSubmitted && !string.IsNullOrWhiteSpace(pcrb.Title) && (userIsOriginator || userIsAdmin)) || + (!pcrbIsSubmitted && pcrb.PlanNumber > 0 && (userIsOriginator || userIsAdmin))) { + + @if (!pcrbIsSubmitted && !string.IsNullOrWhiteSpace(pcrb.Title) && (userIsOriginator || userIsAdmin)) { + + @if (saveInProcess) { + + Processing + } else { + Save + } + + } + @if (!pcrbIsSubmitted && pcrb.PlanNumber > 0 && (userIsOriginator || userIsAdmin)) { + + @if (deleteInProcess) { + + Processing + } else { + Delete + } + + } + + } @if (!pcrbIsSubmitted && GetIncompleteFields().Count() > 0) { IEnumerable incompleteFields = GetIncompleteFields(); @@ -85,7 +107,7 @@ Text="@pcrb.PlanNumber.ToString()" T="int" Disabled="true" - Label="Plan#" + Label="Change#" Required Variant="Variant.Outlined" /> + Label="Project Name" /> - - - + + + + + + + Label="Description Of Change" /> + + @if (pcrb.PlanNumber > 0 && pcrb.CurrentStep > 0) { + + @for (int i = 1; i < 4; i++) { + int current_i = i; + + bool previousStageSubmitted = current_i == 1; + + IEnumerable previousStageApprovals = approvals.Where(a => a.Step == (current_i - 1)); + int previousStageUnsubmittedApprovalCount = previousStageApprovals.Where(a => a.AssignedDate <= DateTimeUtilities.MIN_DT).Count(); + int previousStagePendingApprovalCount = previousStageApprovals.Where(a => a.ItemStatus == 0 && a.AssignedDate > DateTimeUtilities.MIN_DT).Count(); + int previousStageApprovedApprovalCount = previousStageApprovals.Where(a => a.ItemStatus == 1).Count(); + int previousStageDeniedApprovalCount = previousStageApprovals.Where(a => a.ItemStatus == -1).Count(); + + bool previousStageApproved = current_i == 1; + if (!previousStageApproved) { + if (previousStageApprovals.Count() > 0 && previousStageUnsubmittedApprovalCount == 0 && + previousStagePendingApprovalCount == 0 && previousStageApprovedApprovalCount >= 4) + previousStageApproved = true; + } + + if (!previousStageSubmitted) { + if (((previousStagePendingApprovalCount > 0 || previousStageApprovedApprovalCount >= 4) && + previousStageDeniedApprovalCount < previousStageApprovals.Count()) || previousStageApproved) + previousStageSubmitted = true; + } + + IEnumerable currentStageApprovals = approvals.Where(a => a.Step == current_i); + int currentStageUnsubmittedApprovalCount = currentStageApprovals.Where(a => a.AssignedDate <= DateTimeUtilities.MIN_DT).Count(); + int currentStagePendingApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == 0 && a.AssignedDate > DateTimeUtilities.MIN_DT).Count(); + int currentStageApprovedApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == 1).Count(); + int currentStageDeniedApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == -1).Count(); + + bool currentStageApproved = currentStageApprovedApprovalsCount >= 4 && currentStageUnsubmittedApprovalCount == 0 && + currentStagePendingApprovalsCount == 0; + bool currentStageSubmitted = (currentStageApprovals.Count() > 0 && currentStagePendingApprovalsCount > 0 && + currentStageDeniedApprovalsCount < currentStageApprovals.Count()) || currentStageApproved; + + IEnumerable currentStageAttachments = attachments.Where(a => a.Step == current_i); + + bool previousStageHasOpenGatedActionItems = actionItems.Where(a => a.Step == (current_i - 1) && + a.ClosedStatus == false && + a.Gating == true).Count() > 0; + + IEnumerable currentStageActionItems = actionItems.Where(a => a.Step == current_i); + int currentStagePendingActionItemCount = currentStageActionItems.Where(a => a.ClosedStatus == false).Count(); + + bool allActionItemsComplete = current_i < 3 || actionItems.Where(a => a.ClosedStatus == false).Count() == 0; + + bool attachmentsMissing = currentStageAttachments.Count() == 0; + bool actionItemsIncomplete = current_i < 3 && currentStagePendingActionItemCount > 0; + bool affectedDocumentsIncomplete = current_i == 3 && pcr3Documents.Where(d => d.CompletedByID <= 0).Count() > 0; + bool approvalsIncomplete = currentStageApprovals.Count() > 0 && currentStagePendingApprovalsCount > 0; + + + + @($"PCR {current_i}") + @if (previousStageSubmitted && (attachmentsMissing || actionItemsIncomplete || + affectedDocumentsIncomplete || approvalsIncomplete)) { + StringBuilder sb = new(); + int missingSectionCount = 0; + sb.Append("Incomplete sections: "); + if (attachmentsMissing) { + missingSectionCount++; + sb.Append("upload PCRB"); + } + if (actionItemsIncomplete) { + if (missingSectionCount > 0) sb.Append(", "); + missingSectionCount++; + sb.Append("action items incomplete"); + } + if (affectedDocumentsIncomplete) { + if (missingSectionCount > 0) sb.Append(", "); + missingSectionCount++; + sb.Append("affected documents not closed"); + } + if (approvalsIncomplete) { + if (missingSectionCount > 0) sb.Append(", "); + sb.Append("approvals still pending"); + } + + @sb.ToString() + + } + @if (actionItemsIncomplete) { + + All actions must be completed before PCR3 is submitted for approval + + } + @if (previousStageHasOpenGatedActionItems) { + + This stage cannot be submitted for approval until previous stage's gated action items are closed + + } + + + + + Supporting Documents + + + + + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && previousStageSubmitted && !currentStageSubmitted) { + @if (current_i == 1) { + + Download PCRB Template + + } + + @if (attachmentUploadInProcess) { + + Processing + } else { + Upload Document + } + + } + + + + + + File Name + + + + + Uploaded By + + + + + Uploaded Date + + + + + + + @context.FileName + + + @(context.UploadedBy is null ? string.Empty : context.UploadedBy.GetFullName()) + @context.UploadDateTime.ToString("yyyy-MM-dd HH:mm") + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && previousStageSubmitted && !currentStageSubmitted) { + + + @if (deleteAttachmentInProcess) { + + Deleting + } else { + Delete + } + + + } + + + + + + @if (current_i < 3) { + Action Items + @if (!currentStageSubmitted && actionItems.Where(a => a.Step == current_i).Count() == 0) { + + Add action items if applicable + + } + + + + @if (previousStageSubmitted && !currentStageSubmitted) { + + New Action Item + + } + @if (currentStagePendingActionItemCount > 0) { + + Complete All Actions + + } + + + + + + Action + + + + + Responsible Person + + + + Gating + + + + Closed Date + + + + + @context.Name + + @(context.ResponsiblePerson is null ? string.Empty : context.ResponsiblePerson.GetFullName()) + + @(context.Gating ? "Yes" : "No") + + @DateTimeUtilities.GetDateAsStringMaxDefault(context.ClosedDate) + + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && context.ClosedByID == 0) { + + + Update + + @if (!currentStageSubmitted) { + + Delete + + } + + } + + + } else { + int openPCR3Documents = pcr3Documents.Where(d => d.CompletedByID <= 0).Count(); + + Affected Documents + + + + + Document Type + + + + + Document Numbers + + + + Comments + + + ECN# + + + + Closed Date + + + + + Closed By + + + + + @context.DocType + @context.DocNumbers + @context.Comment + @context.GetEcnNumberString() + @(DateTimeUtilities.GetDateAsStringMaxDefault(context.CompletedDate)) + + @(context.CompletedBy is null ? string.Empty : context.CompletedBy.GetFullName()) + + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && !currentStageSubmitted) { + + + Update + + + } + + + } + + + Attendees + + + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && !currentStageSubmitted) { + + + Add Attendee + + + Save Attendance + + + Mark All Attended + + + } + + + + + Attendee Name + + + Attended? + + + + @(context.Attendee is null ? string.Empty : context.Attendee.GetFullName()) + + + + + + + + + Approvers + + + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && previousStageApproved) { + + @if (!currentStageSubmitted) { + + Add Approver + + } + @if (previousStageSubmitted && !currentStageSubmitted && currentStageAttachments.Count() > 0 && + !affectedDocumentsIncomplete && allActionItemsComplete && + !previousStageHasOpenGatedActionItems) { + @if (submitInProcess) { + + Submitting + } else { + + Submit For Approval + + } + } + + } + + + + + Approver Name + + + + + Job Title + + + Status + Assigned Date + Disposition Date + Comments + + + + @(context.User is null ? string.Empty : context.User.GetFullName()) + + @SubRoleCategoryItemToJobTitleConverter(context.SubRoleCategoryItem) + @GetApprovalStatus(context.ItemStatus) + @DateTimeUtilities.GetDateAsStringMinDefault(context.AssignedDate) + @DateTimeUtilities.GetDateAsStringMaxDefault(context.CompletedDate) + @context.Comments + @if (pcrb.ClosedDate >= DateTimeUtilities.MAX_DT && (currentStageUnsubmittedApprovalCount > 0 || + currentStagePendingApprovalsCount > 0)) { + + @if (context.ItemStatus == 0 && userIsAdmin) { + + Update + + } + @if ((current_i < 3 || pcr3Documents.Where(d=>d.CompletedByID == 0).Count() == 0) && + (authStateProvider.CurrentUser is not null && context.UserID == authStateProvider.CurrentUser.UserID) && + context.ItemStatus == 0 && context.AssignedDate > DateTimeUtilities.MIN_DT) { + + Approve + + + Deny + + } + + } + + + + + + } + + } } + + + + @code { [Parameter] public string planNumber { get; set; } = ""; @@ -170,20 +680,142 @@ private int planNumberInt = 0; private PCRB pcrb = null; + private IEnumerable approvals = new List(); + private IEnumerable attendees = new List(); + private IEnumerable attachments = new List(); + private IEnumerable actionItems = new List(); + private IEnumerable pcr3Documents = new List(); + + private IEnumerable qualityApproverUserIds = new List(); + private IEnumerable allActiveUsers = new List(); private User selectedOwner = null; private bool processing = false; private bool saveInProcess = false; + private bool deleteInProcess = false; + private bool submitInProcess = false; + private bool approvalInProcess = false; + private bool denialInProcess = false; + private bool recallInProcess = false; + private bool attachmentUploadInProcess = false; + private bool updateAttachmentInProcess = false; + private bool deleteAttachmentInProcess = false; + private bool addActionItemInProcess = false; + + private string attachmentSearchString = ""; + + private string actionItemSearchString = ""; protected override async Task OnParametersSetAsync() { processing = true; try { allActiveUsers = await userService.GetAllActiveUsers(); + if (qualityApproverUserIds.Count() == 0) + qualityApproverUserIds = await GetQualityApproverUserIds(); + if (!string.IsNullOrWhiteSpace(planNumber) && Int32.TryParse(planNumber, out planNumberInt)) { pcrb = await pcrbService.GetPCRBByPlanNumber(planNumberInt, false); + approvals = await approvalService.GetApprovalsForIssueId(planNumberInt, false); + attendees = await pcrbService.GetAttendeesByPlanNumber(planNumberInt, true); + attachments = await pcrbService.GetAttachmentsByPlanNumber(planNumberInt, false); + actionItems = await pcrbService.GetActionItemsForPlanNumber(planNumberInt, false); + pcr3Documents = await pcrbService.GetPCR3DocumentsForPlanNumber(planNumberInt, false); + + List createPCR3DocumentTasks = new(); + if (pcr3Documents.Count() <= 0) { + List docTypes = new() { "FMEA", "SOPs", "Work Instructions", "Forms/OCAPs", "OpenInsight", + "SPC Charts", "Spare Parts Addition", "Metrology", "Safety (system/documents)", + "Other" + }; + + foreach (string docType in docTypes) { + PCR3Document document = new() { + PlanNumber = planNumberInt, + DocType = docType + }; + + createPCR3DocumentTasks.Add(pcrbService.CreatePCR3Document(document)); + } + } + + List generateAttendeesTasks = new(); + if (attendees.Count() == 0) { + for (int stepNo = 1; stepNo < 4; stepNo++) { + generateAttendeesTasks.Add(GenerateAttendeesForStep(stepNo)); + } + } + + List generateApprovalsTasks = new(); + if (approvals.Count() == 0) { + for (int stepNo = 1; stepNo < 4; stepNo++) { + generateApprovalsTasks.Add(GenerateApprovalsForStep(stepNo)); + } + } + + await Task.WhenAll(createPCR3DocumentTasks); + + pcr3Documents = await pcrbService.GetPCR3DocumentsForPlanNumber(planNumberInt, true); + + await Task.WhenAll(generateAttendeesTasks); + + attendees = await pcrbService.GetAttendeesByPlanNumber(planNumberInt, true); + + await Task.WhenAll(generateApprovalsTasks); + + approvals = await approvalService.GetApprovalsForIssueId(planNumberInt, true); + if (pcrb.OwnerID > 0) selectedOwner = await userService.GetUserByUserId(pcrb.OwnerID); + + if (pcrb.CurrentStep > 0 && pcrb.CurrentStep < 4) { + bool stageHasAdvanced = false; + for (int stage = pcrb.CurrentStep; stage < 4; stage++) { + int current_stage = stage; + if (pcrb.CurrentStep == current_stage) { + IEnumerable currentStageApprovals = approvals.Where(a => a.Step == current_stage); + int currentStagePendingApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == 0).Count(); + int currentStageApprovedApprovalsCount = currentStageApprovals.Where(a => a.ItemStatus == 1).Count(); + bool currentStageApproved = currentStageApprovedApprovalsCount >= 3 && currentStagePendingApprovalsCount == 0; + + if (currentStageApproved) { + if (pcrb.CurrentStep == 3) { + int openActionItemCount = actionItems.Where(a => a.ClosedByID == 0).Count(); + int openAffectedDocumentsCount = pcr3Documents.Where(d => d.CompletedByID == 0).Count(); + + if (openActionItemCount == 0 && openAffectedDocumentsCount == 0) { + pcrb.CurrentStep++; + stageHasAdvanced = true; + } + } else { + pcrb.CurrentStep++; + stageHasAdvanced = true; + } + } + } + } + + if (stageHasAdvanced) { + if (pcrb.CurrentStep == 4) { + pcrb.ClosedDate = DateTime.Now; + + string message = $"PCRB# {pcrb.PlanNumber} - {pcrb.Title} is complete"; + + PCRBNotification notification = new() { + PCRB = pcrb, + Message = message + }; + + await pcrbService.NotifyOriginator(notification); + } + + await pcrbService.UpdatePCRB(pcrb); + pcrb = await pcrbService.GetPCRBByPlanNumber(pcrb.PlanNumber, true); + + StateHasChanged(); + await OnParametersSetAsync(); + } + } } else { int ownerID = 0; string ownerName = string.Empty; @@ -199,10 +831,12 @@ CurrentStep = 0 }; } + + processing = false; } catch (Exception ex) { + processing = false; snackbar.Add(ex.Message, Severity.Error); } - processing = false; } private Func UserToNameConverter = u => u is null ? string.Empty : u.GetFullName(); @@ -222,42 +856,916 @@ if (string.IsNullOrWhiteSpace(pcrb.ChangeLevel)) incompleteFields.Add("Change Level"); if (string.IsNullOrWhiteSpace(pcrb.ChangeDescription)) - incompleteFields.Add("Description"); + incompleteFields.Add("Description of Change"); if (string.IsNullOrWhiteSpace(pcrb.ReasonForChange)) incompleteFields.Add("Reason For Change"); return incompleteFields; } + private bool PCRBReadyToSubmit(int step) { + bool readyToSubmit = GetIncompleteFields().Count() <= 0; + + readyToSubmit = readyToSubmit && pcrb.CurrentStep > 0; + + readyToSubmit = readyToSubmit && attachments.Where(a => a.Step == step).Count() > 0; + + return readyToSubmit; + } + + private bool UserIsApprover() { + bool userIsApprover = approvals.Where(a => authStateProvider.CurrentUser is not null && + a.UserID == authStateProvider.CurrentUser.UserID && + a.ItemStatus == 0).Count() > 0; + + return userIsApprover; + } + private async void SavePCRB() { - saveInProcess = true; + if (!saveInProcess) { + saveInProcess = true; + try { + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + int initialPlanNumber = pcrb.PlanNumber; + + pcrb.OwnerID = selectedOwner.UserID; + pcrb.OwnerName = selectedOwner.GetFullName(); + + if (pcrb.CurrentStep == 0 && GetIncompleteFields().Count() <= 0) + pcrb.CurrentStep++; + + if (initialPlanNumber <= 0) { + await pcrbService.CreateNewPCRB(pcrb); + } else { + await pcrbService.UpdatePCRB(pcrb); + } + + pcrb = await pcrbService.GetPCRBByTitle(pcrb.Title, true); + + cache.Set("redirectUrl", $"pcrb/{pcrb.PlanNumber}"); + + saveInProcess = false; + StateHasChanged(); + snackbar.Add($"PCRB {pcrb.PlanNumber} successfully saved", Severity.Success); + + if (initialPlanNumber <= 0) + navigationManager.NavigateTo($"pcrb/{pcrb.PlanNumber}"); + } catch (Exception ex) { + saveInProcess = false; + snackbar.Add($"Unable to save PCRB, because {ex.Message}", Severity.Error); + } + } + } + + private async Task DeletePCRB() { + if (!deleteInProcess) { + deleteInProcess = true; + try { + bool? result = await dialogService.ShowMessageBox( + "Warning", + $"Are you sure you want to delete PCRB# {pcrb.PlanNumber}?", + yesText: "Yes", noText: "No" + ); + + if (result == true) { + + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + await pcrbService.DeletePCRB(pcrb.PlanNumber); + + await pcrbService.GetAllPCRBs(true); + + deleteInProcess = false; + + snackbar.Add("PCRB successfully deleted", Severity.Success); + + cache.Set("redirectUrl", "pcrb/all"); + navigationManager.NavigateTo($"pcrb/all"); + } + } catch (Exception ex) { + deleteInProcess = false; + snackbar.Add(ex.Message, Severity.Error); + } + } + } + + private async Task> GetQualityApproverUserIds() { + List qualityApproverUserIds = new(); + + try { + int roleId = await approvalService.GetRoleIdForRoleName("Module Manager"); + + if (roleId <= 0) throw new Exception($"could not find Module Manager role ID"); + + IEnumerable subRoles = await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId); + + foreach (SubRole subRole in subRoles) { + if (subRole.SubRoleCategoryItem.Equals("Quality", StringComparison.InvariantCultureIgnoreCase)) { + IEnumerable subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + foreach (User user in subRoleMembers) qualityApproverUserIds.Add(user.UserID); + } + } + + string roleName = "QA_PRE_APPROVAL"; + string subRoleName = "QA_PRE_APPROVAL"; + + roleId = await approvalService.GetRoleIdForRoleName(roleName); + + if (roleId <= 0) throw new Exception($"could not find {roleName} role ID"); + + subRoles = await approvalService.GetSubRolesForSubRoleName(subRoleName, roleId); + + foreach (SubRole subRole in subRoles) { + IEnumerable subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + foreach (User user in subRoleMembers) qualityApproverUserIds.Add(user.UserID); + } + + return qualityApproverUserIds; + } catch (Exception ex) { + snackbar.Add($"Unable to get Quality approvers, because {ex.Message}", Severity.Error); + return qualityApproverUserIds; + } + } + + private async Task GenerateApprovalsForStep(int step) { try { if (pcrb is null) throw new Exception("PCRB cannot be null"); - int initialPlanNumber = pcrb.PlanNumber; + int roleId = await approvalService.GetRoleIdForRoleName("QA_PRE_APPROVAL"); - pcrb.OwnerID = selectedOwner.UserID; - pcrb.OwnerName = selectedOwner.GetFullName(); + if (roleId <= 0) throw new Exception($"could not find QA_PRE_APPROVAL role ID"); - if (initialPlanNumber <= 0) { - await pcrbService.CreateNewPCRB(pcrb); - } else { - await pcrbService.UpdatePCRB(pcrb); + IEnumerable subRoles = await approvalService.GetSubRolesForSubRoleName("QA_PRE_APPROVAL", roleId); + + foreach (SubRole subRole in subRoles) { + IEnumerable members = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + + foreach (User member in members) { + Approval approval = new() { + IssueID = pcrb.PlanNumber, + RoleName = subRole.SubRoleCategoryItem, + SubRole = subRole.SubRoleName, + UserID = member.UserID, + SubRoleID = subRole.SubRoleID, + AssignedDate = DateTimeUtilities.MIN_DT, + Step = step + }; + + await approvalService.CreateApproval(approval); + } } - pcrb = await pcrbService.GetPCRBByTitle(pcrb.Title, true); + roleId = await approvalService.GetRoleIdForRoleName("Module Manager"); - cache.Set("redirectUrl", $"pcrb/{pcrb.PlanNumber}"); + if (roleId <= 0) throw new Exception($"could not find Module Manager role ID"); - saveInProcess = false; - StateHasChanged(); - snackbar.Add($"PCRB {pcrb.PlanNumber} successfully saved", Severity.Success); + subRoles = await approvalService.GetSubRolesForSubRoleName("MMSubRole", roleId); - if (initialPlanNumber <= 0) - navigationManager.NavigateTo($"pcrb/{pcrb.PlanNumber}"); + HashSet subRoleCategoryItems = new() { "Si Production", "Si Engineering", "Quality" }; + foreach (SubRole subRole in subRoles) { + if (subRoleCategoryItems.Contains(subRole.SubRoleCategoryItem)) { + IEnumerable subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + + foreach (User member in subRoleMembers) { + Approval approval = new() { + IssueID = pcrb.PlanNumber, + RoleName = subRole.SubRoleCategoryItem, + SubRole = subRole.SubRoleName, + UserID = member.UserID, + SubRoleID = subRole.SubRoleID, + AssignedDate = DateTimeUtilities.MIN_DT, + Step = step + }; + + await approvalService.CreateApproval(approval); + } + } + } } catch (Exception ex) { - saveInProcess = false; - snackbar.Add($"Unable to save PCRB, because {ex.Message}", Severity.Error); + snackbar.Add($"Unable to generate approvals for step {step}, because {ex.Message}", Severity.Error); } } + + private async Task SubmitForApproval(int step) { + if (!submitInProcess) { + try { + submitInProcess = true; + + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + if (!PCRBReadyToSubmit(step)) + throw new Exception($"PCR {step} not ready to submit"); + + List attendeeTasks = new(); + foreach (PCRBAttendee attendee in attendees.Where(a => a.Step == step)) { + attendeeTasks.Add(pcrbService.UpdateAttendee(attendee)); + } + + await Task.WhenAll(attendeeTasks); + + List notifyResponsiblePersonsTasks = new(); + foreach (PCRBActionItem actionItem in actionItems.Where(a => a.Step == step && + a.NotifyDate <= DateTimeUtilities.MIN_DT && + a.ClosedStatus == false)) { + StringBuilder messageBuilder = new(); + messageBuilder.Append($"PCRB# {pcrb.PlanNumber} [{pcrb.Title}] PCR{step} has an action item for you to complete. "); + messageBuilder.Append($"The action is {actionItem.Name}."); + + PCRBActionItemNotification notification = new() { + PCRB = pcrb, + ActionItem = actionItem, + Message = messageBuilder.ToString() + }; + + actionItem.NotifyDate = DateTime.Now; + + notifyResponsiblePersonsTasks.Add(pcrbService.NotifyResponsiblePerson(notification)); + notifyResponsiblePersonsTasks.Add(pcrbService.UpdateActionItem(actionItem)); + } + + await Task.WhenAll(notifyResponsiblePersonsTasks); + + IEnumerable currentStageApprovals = approvals.Where(a => a.Step == step); + IEnumerable currentStageManagerApprovals = currentStageApprovals.Where(a => !a.SubRoleCategoryItem.Equals("QA Pre Approver")); + int currentStageUnsubmittedApprovalCount = currentStageManagerApprovals.Where(a => a.AssignedDate <= DateTimeUtilities.MIN_DT).Count(); + int currentStagePendingApprovalsCount = currentStageManagerApprovals.Where(a => a.ItemStatus == 0 && a.AssignedDate > DateTimeUtilities.MIN_DT).Count(); + int currentStageApprovedApprovalsCount = currentStageManagerApprovals.Where(a => a.ItemStatus == 1).Count(); + int currentStageDeniedApprovalsCount = currentStageManagerApprovals.Where(a => a.ItemStatus == -1).Count(); + + Approval? latestQaPreApproval = currentStageApprovals + .Where(a => a.SubRoleCategoryItem.Equals("QA Pre Approver")) + .OrderByDescending(a => a.AssignedDate) + .FirstOrDefault(); + + if (latestQaPreApproval is null) throw new Exception("QA pre approval not found"); + + bool qaPreApprovalDenied = latestQaPreApproval.ItemStatus == -1; + if (qaPreApprovalDenied && currentStageUnsubmittedApprovalCount >= 3) { + Approval newApproval = new() { + IssueID = latestQaPreApproval.IssueID, + RoleName = latestQaPreApproval.RoleName, + SubRole = latestQaPreApproval.SubRole, + UserID = latestQaPreApproval.UserID, + SubRoleID = latestQaPreApproval.SubRoleID, + AssignedDate = DateTime.Now, + Step = latestQaPreApproval.Step + }; + + await approvalService.CreateApproval(newApproval); + } else if (currentStageDeniedApprovalsCount > (currentStageUnsubmittedApprovalCount + currentStagePendingApprovalsCount + + currentStageApprovedApprovalsCount)) { + HashSet copiedApprovals = new(); + + List createCopiedApprovalsTasks = new(); + foreach (Approval oldApproval in currentStageApprovals) { + if (!copiedApprovals.Contains($"{oldApproval.RoleName}{oldApproval.UserID}")) { + DateTime assignedDate = DateTimeUtilities.MIN_DT; + + if (oldApproval.RoleName.Equals("QA Pre Approver")) + assignedDate = DateTime.Now; + + Approval newApproval = new() { + IssueID = oldApproval.IssueID, + RoleName = oldApproval.RoleName, + SubRole = oldApproval.SubRole, + UserID = oldApproval.UserID, + SubRoleID = oldApproval.SubRoleID, + AssignedDate = assignedDate, + Step = oldApproval.Step + }; + + createCopiedApprovalsTasks.Add(approvalService.CreateApproval(newApproval)); + + copiedApprovals.Add($"{oldApproval.RoleName}{oldApproval.UserID}"); + } + } + + await Task.WhenAll(createCopiedApprovalsTasks); + } else { + Approval? unassignedQaPreApproval = currentStageApprovals.Where(a => a.SubRoleCategoryItem.Equals("QA Pre Approver") && + a.AssignedDate <= DateTimeUtilities.MIN_DT).FirstOrDefault(); + + if (unassignedQaPreApproval is null) throw new Exception("unassigned QA pre approval not found"); + + unassignedQaPreApproval.AssignedDate = DateTime.Now; + await approvalService.UpdateApproval(unassignedQaPreApproval); + } + + approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, true); + + await pcrbService.NotifyNewApprovals(pcrb); + + if (pcrb.CurrentStep == 1) { + pcrb.InsertTimeStamp = DateTime.Now; + await pcrbService.UpdatePCRB(pcrb); + + pcrb = await pcrbService.GetPCRBByPlanNumber(pcrb.PlanNumber, true); + } + + submitInProcess = false; + + snackbar.Add("PCRB successfully submitted for approval", Severity.Success); + } catch (Exception ex) { + submitInProcess = false; + snackbar.Add($"Unable to submit for approval, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task SetUserForApproval(Approval approval, User user) { + if (approval is null) throw new ArgumentNullException("approval cannot be null"); + if (user is null) throw new ArgumentNullException("user cannot be null"); + + if (approval.CompletedDate < DateTimeUtilities.MAX_DT || approval.ItemStatus != 0) + throw new ArgumentException("cannot reassign a complete approval"); + + approval.UserID = user.UserID; + approval.User = user; + approval.NotifyDate = DateTimeUtilities.MIN_DT; + + await approvalService.UpdateApproval(approval); + await approvalService.GetApprovalsForIssueId(approval.IssueID, true); + + await pcrbService.NotifyNewApprovals(pcrb); + } + + private async Task ApprovePCR(int step) { + if (!processing) { + try { + processing = true; + + if (step <= 0 || step > 3) + throw new Exception($"{step} is not a valid PCR#"); + + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + if (pcrb.ClosedDate < DateTimeUtilities.MAX_DT) + throw new Exception("cannot approve a complete PCRB"); + + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + return; + } + + IEnumerable activeApprovalsForUser = approvals.Where(a => a.UserID == authStateProvider.CurrentUser.UserID && + a.AssignedDate > DateTimeUtilities.MIN_DT && + a.ItemStatus == 0 && + a.Step == step); + + if (activeApprovalsForUser.Count() <= 0) + throw new Exception($"you have no active approvals for PCR{step}"); + + string? comments = ""; + + DialogParameters parameters = new DialogParameters { { x => x.comments, comments } }; + var dialog = dialogService.Show($"Approval Comments", parameters); + + var result = await dialog.Result; + + if (result.Canceled) throw new Exception("you must provide approval comments"); + + comments = result.Data.ToString(); + + foreach (Approval approval in activeApprovalsForUser) { + approval.CompletedDate = DateTime.Now; + approval.Comments = comments is null ? "" : comments; + approval.ItemStatus = 1; + + await approvalService.UpdateApproval(approval); + } + + approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, false); + + IEnumerable unassignedApprovals = approvals.Where(a => a.Step == step && a.AssignedDate <= DateTimeUtilities.MIN_DT); + if (unassignedApprovals.Count() > 0) { + List assignmentTasks = new(); + foreach (Approval approval in unassignedApprovals) { + approval.AssignedDate = DateTime.Now; + assignmentTasks.Add(approvalService.UpdateApproval(approval)); + } + await Task.WhenAll(assignmentTasks); + + approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, false); + + await pcrbService.NotifyNewApprovals(pcrb); + } + + IEnumerable remainingActiveApprovals = approvals.Where(a => a.Step == step && + a.ItemStatus == 0); + + if (remainingActiveApprovals.Count() <= 0) { + StringBuilder messageBuilder = new(); + messageBuilder.Append($"PCRB# {pcrb.PlanNumber} - {pcrb.Title} - PCR{step} has been approved."); + + PCRBNotification notification = new() { + PCRB = pcrb, + Message = messageBuilder.ToString() + }; + + await pcrbService.NotifyOriginator(notification); + } + + processing = false; + + snackbar.Add($"PCR{step} successfully approved", Severity.Success); + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to approve PCR{step}, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task DenyPCR(int step) { + if (!processing) { + try { + processing = true; + + if (step <= 0 || step > 3) + throw new Exception($"{step} is not a valid PCR#"); + + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + if (pcrb.ClosedDate < DateTimeUtilities.MAX_DT) + throw new Exception("cannot deny a complete PCRB"); + + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + return; + } + + IEnumerable activeApprovalsForUser = approvals.Where(a => a.UserID == authStateProvider.CurrentUser.UserID && + a.AssignedDate > DateTimeUtilities.MIN_DT && + a.ItemStatus == 0 && + a.Step == step); + + if (activeApprovalsForUser.Count() <= 0) + throw new Exception("you have no active approvals"); + + string? comments = ""; + + DialogParameters parameters = new DialogParameters { { x => x.comments, comments } }; + var dialog = dialogService.Show($"Denial Comments", parameters); + + var result = await dialog.Result; + + if (result.Canceled) throw new Exception("you must provide a comment"); + + comments = result.Data.ToString(); + + IEnumerable outstandingActiveApprovalsForStep = approvals.Where(a => a.Step == step && + a.ItemStatus == 0 && + a.AssignedDate > DateTimeUtilities.MIN_DT); + + foreach (Approval approval in outstandingActiveApprovalsForStep) { + approval.CompletedDate = DateTime.Now; + approval.Comments = comments is null ? "" : comments; + approval.ItemStatus = -1; + + await approvalService.UpdateApproval(approval); + } + + approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, false); + + if (step == 1) { + pcrb.InsertTimeStamp = DateTimeUtilities.MIN_DT; + await pcrbService.UpdatePCRB(pcrb); + } + + StringBuilder messageBuilder = new(); + messageBuilder.Append($"PCRB# {pcrb.PlanNumber} - {pcrb.Title} - PCR{step} has been denied "); + messageBuilder.Append($"by {authStateProvider.CurrentUser.GetFullName()}. "); + messageBuilder.AppendLine(""); + messageBuilder.Append($"Comments: {comments}"); + + PCRBNotification notification = new() { + PCRB = pcrb, + Message = messageBuilder.ToString() + }; + + await pcrbService.NotifyOriginator(notification); + + processing = false; + + snackbar.Add($"PCR{step} successfully denied", Severity.Success); + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to deny PCR{step}, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task UploadAttachment(int step) { + if (!attachmentUploadInProcess) { + attachmentUploadInProcess = true; + try { + if (step <= 0) throw new ArgumentException($"{step} is not a valid PCRB stage#"); + + DialogParameters parameters = new DialogParameters { + { x => x.planNumber, pcrb.PlanNumber }, + { x => x.step, step } + }; + + var dialog = dialogService.Show("Upload Attachment", parameters); + + var result = await dialog.Result; + + if (result is not null && !result.Canceled) + snackbar.Add("Attachment successfully uploaded", Severity.Success); + + attachments = await pcrbService.GetAttachmentsByPlanNumber(planNumberInt, true); + + attachmentUploadInProcess = false; + } catch (Exception ex) { + attachmentUploadInProcess = false; + snackbar.Add($"Unable to upload attachment, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task DeleteAttachment(PCRBAttachment attachment) { + try { + processing = true; + + if (attachment is null) + throw new ArgumentNullException("attachment cannot be null"); + + bool? result = await dialogService.ShowMessageBox( + "Warning", + $"Are you sure you want to delete PCRB attachment {attachment.FileName}?", + yesText: "Yes", noText: "No" + ); + + if (result == true) { + await pcrbService.DeleteAttachment(attachment); + + attachments = await pcrbService.GetAttachmentsByPlanNumber(attachment.PlanNumber, true); + } + + processing = false; + + snackbar.Add("Attachment successfully deleted", Severity.Success); + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to delete attachment, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + + private async Task CreateNewActionItem(int step) { + try { + DialogParameters parameters = new DialogParameters { + { x => x.planNumber, pcrb.PlanNumber }, + { x => x.step, step }, + { x => x.allActiveUsers, allActiveUsers } + }; + + var dialog = dialogService.Show("New Action Item", parameters); + + var result = await dialog.Result; + + if (result is not null && !result.Canceled) + snackbar.Add("Action item successfully created", Severity.Success); + + actionItems = await pcrbService.GetActionItemsForPlanNumber(pcrb.PlanNumber, true); + } catch (Exception ex) { + snackbar.Add($"Unable to create new action item, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + + private async Task CloseAllActionItems(int step) { + if (!processing) { + try { + processing = true; + + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + return; + } + + IEnumerable outstandingActionItemsForStep = + actionItems.Where(a => a.Step == step && a.ClosedStatus == false); + + foreach (PCRBActionItem actionItem in outstandingActionItemsForStep) { + actionItem.ClosedStatus = true; + actionItem.ClosedDate = DateTime.Now; + actionItem.ClosedBy = authStateProvider.CurrentUser; + actionItem.ClosedByID = authStateProvider.CurrentUser.UserID; + + await pcrbService.UpdateActionItem(actionItem); + } + + actionItems = await pcrbService.GetActionItemsForPlanNumber(planNumberInt, true); + + processing = false; + + snackbar.Add("Successfully approved all action items", Severity.Success); + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to approve all action items, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task UpdateActionItem(PCRBActionItem actionItem) { + try { + if (actionItem is null) + throw new ArgumentNullException("action item cannot be null"); + + DialogParameters parameters = new DialogParameters { + { x => x.planNumber, pcrb.PlanNumber }, + { x => x.step, actionItem.Step }, + { x => x.allActiveUsers, allActiveUsers }, + { x => x.actionItem, actionItem } + }; + + var dialog = dialogService.Show("Update Action Item", parameters); + + var result = await dialog.Result; + + if (result is not null && !result.Canceled) + snackbar.Add("Action item successfully updated", Severity.Success); + + actionItems = await pcrbService.GetActionItemsForPlanNumber(actionItem.PlanNumber, true); + } catch (Exception ex) { + snackbar.Add($"Unable to update action item, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + + private async Task DeleteActionItem(PCRBActionItem actionItem) { + try { + if (actionItem is null) + throw new ArgumentNullException("action item cannot be null"); + + bool? result = await dialogService.ShowMessageBox( + "Warning", + $"Are you sure you want to delete PCRB action item {actionItem.Name}?", + yesText: "Yes", noText: "No" + ); + + if (result == true) { + await pcrbService.DeleteActionItem(actionItem.ID); + + actionItems = await pcrbService.GetActionItemsForPlanNumber(pcrb.PlanNumber, true); + + snackbar.Add("Action item successfully deleted", Severity.Success); + } + } catch (Exception ex) { + snackbar.Add($"Unable to delete action item, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + + private async Task UpdatePCR3Document(PCR3Document document) { + if (!processing) { + try { + processing = true; + + if (document is null) throw new Exception("affected document cannot be null"); + + DialogParameters parameters = new DialogParameters { + { x => x.document, document }, + }; + + var dialog = dialogService.Show("Update Affected Document Type", parameters); + + var result = await dialog.Result; + + processing = false; + + if (result is not null && !result.Canceled) + snackbar.Add("Affected document successfully updated", Severity.Success); + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to update affected document, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task GenerateAttendeesForStep(int step) { + try { + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + int roleId = await approvalService.GetRoleIdForRoleName("PCRB Attendee"); + + if (roleId <= 0) throw new Exception($"could not find PCRB Attendee role ID"); + + IEnumerable subRoles = await approvalService.GetSubRolesForSubRoleName("PCRBAttendee", roleId); + + foreach (SubRole subRole in subRoles) { + IEnumerable subRoleMembers = await approvalService.GetApprovalGroupMembers(subRole.SubRoleID); + + foreach (User member in subRoleMembers) { + PCRBAttendee attendee = new() { + PlanNumber = pcrb.PlanNumber, + AttendeeID = member.UserID, + Attendee = member, + Step = step + }; + + await pcrbService.CreateNewAttendee(attendee); + } + } + } catch (Exception ex) { + snackbar.Add($"Unable to generate attendees for step {step}, because {ex.Message}", Severity.Error); + } + } + + private async Task UpdateAttendees(IEnumerable pcrAttendees, int step) { + if (!processing) { + try { + processing = true; + + List attendeeTasks = new(); + foreach(PCRBAttendee attendee in pcrAttendees) { + attendeeTasks.Add(pcrbService.UpdateAttendee(attendee)); + } + + await Task.WhenAll(attendeeTasks); + + processing = false; + snackbar.Add($"PCR{step} attendees updated", Severity.Success); + } catch (Exception ex) { + processing = false; + snackbar.Add($"", Severity.Error); + } + } + } + + private async Task AddAttendee(int step) { + if (!processing) { + try { + processing = true; + + if (authStateProvider.CurrentUser is null) { + await authStateProvider.Logout(); + navigationManager.NavigateTo("login"); + return; + } + + if (pcrb is null) throw new Exception("PCRB cannot be null"); + + if (pcrb.PlanNumber == 0) throw new Exception("PCRB was never saved"); + + User user = authStateProvider.CurrentUser; + + DialogParameters parameters = new DialogParameters { + { x => x.selectedUser, user } + }; + + var dialog = dialogService.Show("Add Attendee", parameters); + + var result = await dialog.Result; + + if (result is not null && !result.Canceled && result.Data is not null) { + user = (User)result.Data; + + if (attendees.Where(a => a.AttendeeID == user.UserID).Count() == 0) { + PCRBAttendee attendee = new() { + PlanNumber = pcrb.PlanNumber, + Attendee = user, + AttendeeID = user.UserID, + Step = step + }; + + await pcrbService.CreateNewAttendee(attendee); + } + } + + attendees = await pcrbService.GetAttendeesByPlanNumber(pcrb.PlanNumber, true); + + processing = false; + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to add attendee, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task MarkAllAttended(int step) { + if (!processing) { + try { + processing = true; + + List updateAttendeeTasks = new(); + + foreach (PCRBAttendee attendee in attendees.Where(a => a.Step == step)) { + attendee.Attended = true; + updateAttendeeTasks.Add(pcrbService.UpdateAttendee(attendee)); + } + + await Task.WhenAll(updateAttendeeTasks); + + processing = false; + } catch (Exception ex) { + processing = false; + snackbar.Add($"Unable to mark all attended, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + } + + private async Task AddApprover(int step) { + try { + DialogParameters parameters = new DialogParameters { + { x => x.planNumber, pcrb.PlanNumber }, + { x => x.step, step } + }; + + var dialog = dialogService.Show("Add Approver", parameters); + + var result = await dialog.Result; + + if (result is not null && !result.Canceled) + snackbar.Add("Approver successfully added", Severity.Success); + + approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, false); + } catch (Exception ex) { + snackbar.Add($"Unable to add approver, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + + private async Task UpdateApproval(Approval approval) { + try { + if (approval is null) + throw new ArgumentNullException("approval cannot be null"); + + DialogParameters parameters = new DialogParameters { + { x => x.planNumber, pcrb.PlanNumber }, + { x => x.step, approval.Step }, + { x => x.approval, approval } + }; + + var dialog = dialogService.Show("Update Approval", parameters); + + var result = await dialog.Result; + + if (result is not null && !result.Canceled) + snackbar.Add("Approval successfully updated", Severity.Success); + + approvals = await approvalService.GetApprovalsForIssueId(pcrb.PlanNumber, true); + } catch (Exception ex) { + snackbar.Add($"Unable to update approval, because {ex.Message}", Severity.Error); + } + + StateHasChanged(); + await OnParametersSetAsync(); + } + + private string SubRoleCategoryItemToJobTitleConverter(string subRoleCategoryItem) { + if (string.IsNullOrWhiteSpace(subRoleCategoryItem)) return ""; + + string jobTitle = subRoleCategoryItem.Replace("Si", ""); + + if (!jobTitle.Contains("other", StringComparison.InvariantCultureIgnoreCase) && + !jobTitle.Contains("qa pre approver", StringComparison.InvariantCultureIgnoreCase)) + jobTitle = jobTitle + " Manager"; + + return jobTitle; + } + + private string GetApprovalStatus(int itemStatus) { + if (itemStatus < 0) return "Denied"; + if (itemStatus > 0) return "Approved"; + return "Pending"; + } } \ No newline at end of file diff --git a/MesaFabApproval.Client/Program.cs b/MesaFabApproval.Client/Program.cs index 5ad8f11..7bfe952 100644 --- a/MesaFabApproval.Client/Program.cs +++ b/MesaFabApproval.Client/Program.cs @@ -15,6 +15,9 @@ WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args); string _apiBaseUrl = builder.Configuration["FabApprovalApiBaseUrl"] ?? throw new NullReferenceException("FabApprovalApiBaseUrl not found in config"); +string _oldSiteUrl = builder.Configuration["OldFabApprovalUrl"] ?? + throw new NullReferenceException("OldFabApprovalUrl not found in config"); + builder.Services.AddTransient(); builder.Services @@ -30,6 +33,12 @@ builder.Services }) .AddHttpMessageHandler(); +builder.Services + .AddHttpClient("OldSite", client => { + client.BaseAddress = new Uri(_oldSiteUrl); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*")); + }); + builder.Services.AddMemoryCache(); builder.Services.AddMudServices(config => { diff --git a/MesaFabApproval.Client/Services/ApprovalService.cs b/MesaFabApproval.Client/Services/ApprovalService.cs index 930be63..c1a5744 100644 --- a/MesaFabApproval.Client/Services/ApprovalService.cs +++ b/MesaFabApproval.Client/Services/ApprovalService.cs @@ -289,7 +289,7 @@ public class ApprovalService : IApprovalService { if (members is null || members.Count() <= 0) throw new Exception($"unable to find group members for sub role {subRoleId}"); - _cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddMinutes(15)); + _cache.Set($"approvalMembers{subRoleId}", members, DateTimeOffset.Now.AddMinutes(2)); } else { throw new Exception($"Unable to get group members, because {responseMessage.ReasonPhrase}"); } diff --git a/MesaFabApproval.Client/Services/AuthenticationService.cs b/MesaFabApproval.Client/Services/AuthenticationService.cs index fba7548..bba3ba1 100644 --- a/MesaFabApproval.Client/Services/AuthenticationService.cs +++ b/MesaFabApproval.Client/Services/AuthenticationService.cs @@ -165,9 +165,7 @@ public class AuthenticationService : IAuthenticationService { await _localStorageService.AddItem("MesaFabApprovalUserId", loginId); } - public async Task SetCurrentUser(User user) { - if (user is null) throw new ArgumentNullException("User cannot be null"); - + public async Task SetCurrentUser(User? user) { _cache.Set("MesaFabApprovalCurrentUser", user); await _localStorageService.AddItem("MesaFabApprovalCurrentUser", user); } @@ -182,8 +180,10 @@ public class AuthenticationService : IAuthenticationService { public async Task GetCurrentUser() { User? currentUser = null; - currentUser = _cache.Get("MesaFabApprovalCurrentUser") ?? - await _localStorageService.GetItem("MesaFabApprovalCurrentUser"); + currentUser = _cache.Get("MesaFabApprovalCurrentUser"); + + if (currentUser is null) + currentUser = await _localStorageService.GetItem("MesaFabApprovalCurrentUser"); return currentUser; } diff --git a/MesaFabApproval.Client/Services/MesaFabApprovalAuthStateProvider.cs b/MesaFabApproval.Client/Services/MesaFabApprovalAuthStateProvider.cs index 09de7a6..353a83d 100644 --- a/MesaFabApproval.Client/Services/MesaFabApprovalAuthStateProvider.cs +++ b/MesaFabApproval.Client/Services/MesaFabApprovalAuthStateProvider.cs @@ -46,6 +46,16 @@ public class MesaFabApprovalAuthStateProvider : AuthenticationStateProvider, IDi CurrentUser = await _authService.GetCurrentUser(); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal))); + + if (CurrentUser is null) { + string loginId = _userService.GetLoginIdFromClaimsPrincipal(principal); + + User? user = await _userService.GetUserByLoginId(loginId); + + await _authService.SetCurrentUser(user); + + NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal))); + } } public async Task LoginAsync(string loginId, string password) { diff --git a/MesaFabApproval.Client/Services/PCRBService.cs b/MesaFabApproval.Client/Services/PCRBService.cs index 042cc76..42a6ae5 100644 --- a/MesaFabApproval.Client/Services/PCRBService.cs +++ b/MesaFabApproval.Client/Services/PCRBService.cs @@ -1,8 +1,11 @@ -using System.Text; +using System.Net.Http.Headers; +using System.Text; using System.Text.Json; using MesaFabApproval.Shared.Models; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Caching.Memory; using MudBlazor; @@ -11,28 +14,52 @@ namespace MesaFabApproval.Client.Services; public interface IPCRBService { Task IdIsValid(string id); + Task IdIsValid(int id); Task> GetAllPCRBs(bool bypassCache); Task CreateNewPCRB(PCRB pcrb); Task GetPCRBByPlanNumber(int planNumber, bool bypassCache); Task GetPCRBByTitle(string title, bool bypassCache); Task UpdatePCRB(PCRB pcrb); Task DeletePCRB(int planNumber); + Task UploadAttachment(PCRBAttachment attachment); + Task> GetAttachmentsByPlanNumber(int planNumber, bool bypassCache); + Task UpdateAttachment(PCRBAttachment attachment); + Task DeleteAttachment(PCRBAttachment attachment); + Task CreateNewActionItem(PCRBActionItem actionItem); + Task UpdateActionItem(PCRBActionItem actionItem); + Task DeleteActionItem(int id); + Task> GetActionItemsForPlanNumber(int planNumber, bool bypassCache); + Task CreateNewAttendee(PCRBAttendee attendee); + Task UpdateAttendee(PCRBAttendee attendee); + Task DeleteAttendee(int id); + Task> GetAttendeesByPlanNumber(int planNumber, bool bypassCache); + Task CreatePCR3Document(PCR3Document document); + Task UpdatePCR3Document(PCR3Document document); + Task> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache); + Task NotifyNewApprovals(PCRB pcrb); + Task NotifyApprovers(PCRBNotification notification); + Task NotifyOriginator(PCRBNotification notification); + Task NotifyResponsiblePerson(PCRBActionItemNotification notification); } public class PCRBService : IPCRBService { private readonly IMemoryCache _cache; private readonly IHttpClientFactory _httpClientFactory; private readonly ISnackbar _snackbar; + private readonly IUserService _userService; - public PCRBService(IMemoryCache cache, IHttpClientFactory httpClientFactory, ISnackbar snackbar) { + public PCRBService(IMemoryCache cache, + IHttpClientFactory httpClientFactory, + ISnackbar snackbar, + IUserService userService) { _cache = cache ?? throw new ArgumentNullException("IMemoryCache not injected"); _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException("IHttpClientFactory not injected"); _snackbar = snackbar ?? throw new ArgumentNullException("ISnackbar not injected"); + _userService = userService ?? throw new ArgumentNullException("IUserService not injected"); } public async Task IdIsValid(string id) { bool isMatch = true; - if (string.IsNullOrWhiteSpace(id)) isMatch = false; try { @@ -63,6 +90,37 @@ public class PCRBService : IPCRBService { return null; } + public async Task IdIsValid(int id) { + bool isMatch = true; + if (id <= 0) isMatch = false; + + try { + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/getByPlanNumber?planNumber={id}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (responseMessage.IsSuccessStatusCode) { + string responseContent = await responseMessage.Content.ReadAsStringAsync(); + + JsonSerializerOptions jsonSerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + + PCRB? pcrb = JsonSerializer.Deserialize(responseContent, jsonSerializerOptions); + + if (pcrb is null) isMatch = false; + } else { + isMatch = false; + } + } catch (Exception) { + isMatch = false; + } + + return isMatch; + } + public async Task> GetAllPCRBs(bool bypassCache) { try { IEnumerable? allPCRBs = null; @@ -219,7 +277,7 @@ public class PCRBService : IPCRBService { } public async Task UpdatePCRB(PCRB pcrb) { - if (pcrb is null) throw new ArgumentNullException("MRB cannot be null"); + if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null"); HttpClient httpClient = _httpClientFactory.CreateClient("API"); @@ -251,7 +309,7 @@ public class PCRBService : IPCRBService { HttpClient httpClient = _httpClientFactory.CreateClient("API"); - HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/delete?planNumber={planNumber}"); + HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb?planNumber={planNumber}"); HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); @@ -260,4 +318,450 @@ public class PCRBService : IPCRBService { IEnumerable allPCRBs = await GetAllPCRBs(true); _cache.Set("allPCRBs", allPCRBs); } + + public async Task UploadAttachment(PCRBAttachment attachment) { + if (attachment is null) throw new ArgumentNullException("attachment cannot be null"); + if (attachment.File is null) throw new ArgumentNullException("file cannot be null"); + if (attachment.File.Size <= 0) throw new ArgumentException("file size must be greater than zero"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/attachment"); + + using MultipartFormDataContent content = new MultipartFormDataContent(); + + try { + long maxFileSize = 1024L * 1024L * 1024L * 2L; + StreamContent fileContent = new StreamContent(attachment.File.OpenReadStream(maxFileSize)); + + FileExtensionContentTypeProvider contentTypeProvider = new FileExtensionContentTypeProvider(); + + const string defaultContentType = "application/octet-stream"; + + if (!contentTypeProvider.TryGetContentType(attachment.File.Name, out string? contentType)) { + contentType = defaultContentType; + } + + fileContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); + + content.Add(content: fileContent, name: "\"file\"", fileName: attachment.File.Name); + } catch (Exception ex) { + _snackbar.Add(ex.Message); + } + + content.Add(new StringContent(attachment.PlanNumber.ToString()), "PlanNumber"); + content.Add(new StringContent(attachment.FileName), "FileName"); + content.Add(new StringContent(attachment.UploadedByID.ToString()), "UploadedByID"); + content.Add(new StringContent(attachment.Title), "Title"); + content.Add(new StringContent(attachment.UploadDateTime.ToString("yyyy-MM-dd HH:mm:ss")), "UploadDateTime"); + content.Add(new StringContent(attachment.Step.ToString()), "Step"); + + requestMessage.Content = content; + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + + await GetAttachmentsByPlanNumber(attachment.PlanNumber, true); + } + + public async Task> GetAttachmentsByPlanNumber(int planNumber, bool bypassCache) { + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + IEnumerable? attachments = null; + if (!bypassCache) + attachments = _cache.Get>($"pcrbAttachments{planNumber}"); + + if (attachments is null) { + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/attachments?planNumber={planNumber}&bypassCache={bypassCache}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (responseMessage.IsSuccessStatusCode) { + string responseContent = await responseMessage.Content.ReadAsStringAsync(); + + JsonSerializerOptions jsonSerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + + attachments = JsonSerializer.Deserialize>(responseContent, jsonSerializerOptions) ?? + new List(); + + if (attachments.Count() > 0) { + foreach (PCRBAttachment attachment in attachments) { + attachment.UploadedBy = await _userService.GetUserByUserId(attachment.UploadedByID); + } + + _cache.Set($"pcrbAttachments{planNumber}", attachments, DateTimeOffset.Now.AddMinutes(5)); + } + } else { + throw new Exception(responseMessage.ReasonPhrase); + } + } + + return attachments; + } + + public async Task UpdateAttachment(PCRBAttachment attachment) { + if (attachment is null) throw new ArgumentNullException("attachment cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/attachment"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(attachment), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task DeleteAttachment(PCRBAttachment attachment) { + if (attachment is null) throw new ArgumentNullException("attachment cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/attachment"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(attachment), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task CreateNewActionItem(PCRBActionItem actionItem) { + if (actionItem is null) throw new ArgumentNullException("action item cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/actionItem"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(actionItem), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + + await GetActionItemsForPlanNumber(actionItem.PlanNumber, true); + } + + public async Task UpdateActionItem(PCRBActionItem actionItem) { + if (actionItem is null) throw new ArgumentNullException("action item cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/actionItem"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(actionItem), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task DeleteActionItem(int id) { + if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB action item ID"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/actionItem?id={id}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task> GetActionItemsForPlanNumber(int planNumber, bool bypassCache) { + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + IEnumerable? actionItems = null; + if (!bypassCache) + actionItems = _cache.Get>($"pcrbActionItems{planNumber}"); + + if (actionItems is null) { + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/actionItems?planNumber={planNumber}&bypassCache={bypassCache}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (responseMessage.IsSuccessStatusCode) { + string responseContent = await responseMessage.Content.ReadAsStringAsync(); + + JsonSerializerOptions jsonSerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + + actionItems = JsonSerializer.Deserialize>(responseContent, jsonSerializerOptions) ?? + new List(); + + if (actionItems.Count() > 0) { + foreach (PCRBActionItem actionItem in actionItems) { + actionItem.UploadedBy = await _userService.GetUserByUserId(actionItem.UploadedByID); + if (actionItem.ResponsiblePersonID > 0) + actionItem.ResponsiblePerson = await _userService.GetUserByUserId(actionItem.ResponsiblePersonID); + if (actionItem.ClosedByID > 0) + actionItem.ClosedBy = await _userService.GetUserByUserId(actionItem.ClosedByID); + } + + _cache.Set($"pcrbActionItems{planNumber}", actionItems, DateTimeOffset.Now.AddMinutes(5)); + } + } else { + throw new Exception(responseMessage.ReasonPhrase); + } + } + + return actionItems; + } + + public async Task CreateNewAttendee(PCRBAttendee attendee) { + if (attendee is null) throw new ArgumentNullException("attendee cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/attendee"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(attendee), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + + await GetAttendeesByPlanNumber(attendee.PlanNumber, true); + } + + public async Task UpdateAttendee(PCRBAttendee attendee) { + if (attendee is null) throw new ArgumentNullException("attendee cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/attendee"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(attendee), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task DeleteAttendee(int id) { + if (id <= 0) throw new ArgumentException($"{id} is not a valid PCRB attendee ID"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Delete, $"pcrb/attendee?id={id}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task> GetAttendeesByPlanNumber(int planNumber, bool bypassCache) { + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + IEnumerable? attendees = null; + if (!bypassCache) + attendees = _cache.Get>($"pcrbAttendees{planNumber}"); + + if (attendees is null) { + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/attendees?planNumber={planNumber}&bypassCache={bypassCache}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (responseMessage.IsSuccessStatusCode) { + string responseContent = await responseMessage.Content.ReadAsStringAsync(); + + JsonSerializerOptions jsonSerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + + attendees = JsonSerializer.Deserialize>(responseContent, jsonSerializerOptions) ?? + new List(); + + if (attendees.Count() > 0) { + foreach (PCRBAttendee attendee in attendees) + attendee.Attendee = await _userService.GetUserByUserId(attendee.AttendeeID); + + _cache.Set($"pcrbAttendees{planNumber}", attendees, DateTimeOffset.Now.AddMinutes(5)); + } + } else { + throw new Exception(responseMessage.ReasonPhrase); + } + } + + return attendees; + } + + public async Task CreatePCR3Document(PCR3Document document) { + if (document is null) throw new ArgumentNullException("document cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/pcr3Document"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(document), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + + await GetPCR3DocumentsForPlanNumber(document.PlanNumber, true); + } + + public async Task UpdatePCR3Document(PCR3Document document) { + if (document is null) throw new ArgumentNullException("document cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Put, $"pcrb/pcr3Document"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(document), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception(responseMessage.ReasonPhrase); + } + + public async Task> GetPCR3DocumentsForPlanNumber(int planNumber, bool bypassCache) { + if (planNumber <= 0) throw new ArgumentException($"{planNumber} is not a valid PCRB Plan#"); + + IEnumerable? documents = null; + if (!bypassCache) + documents = _cache.Get>($"pcr3Documents{planNumber}"); + + if (documents is null) { + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Get, $"pcrb/pcr3Documents?planNumber={planNumber}&bypassCache={bypassCache}"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (responseMessage.IsSuccessStatusCode) { + string responseContent = await responseMessage.Content.ReadAsStringAsync(); + + JsonSerializerOptions jsonSerializerOptions = new() { + PropertyNameCaseInsensitive = true + }; + + documents = JsonSerializer.Deserialize>(responseContent, jsonSerializerOptions) ?? + new List(); + + if (documents.Count() > 0) { + foreach (PCR3Document document in documents) { + if (document.CompletedByID > 0) + document.CompletedBy = await _userService.GetUserByUserId(document.CompletedByID); + } + + _cache.Set($"pcr3Documents{planNumber}", documents, DateTimeOffset.Now.AddMinutes(5)); + } + } else { + throw new Exception(responseMessage.ReasonPhrase); + } + } + + return documents; + } + + public async Task NotifyNewApprovals(PCRB pcrb) { + if (pcrb is null) throw new ArgumentNullException("PCRB cannot be null"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/notify/new-approvals"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(pcrb), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception($"Unable to notify new PCRB approvers, because {responseMessage.ReasonPhrase}"); + } + + public async Task NotifyApprovers(PCRBNotification notification) { + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/notify/approvers"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(notification), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception($"Unable to notify PCRB approvers, because {responseMessage.ReasonPhrase}"); + } + + public async Task NotifyOriginator(PCRBNotification notification) { + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (notification.PCRB is null) throw new ArgumentNullException("PCRB cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/notify/originator"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(notification), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception($"Unable to notify PCRB originator, because {responseMessage.ReasonPhrase}"); + } + + public async Task NotifyResponsiblePerson(PCRBActionItemNotification notification) { + if (notification is null) throw new ArgumentNullException("notification cannot be null"); + if (string.IsNullOrWhiteSpace(notification.Message)) throw new ArgumentException("message cannot be null or empty"); + + HttpClient httpClient = _httpClientFactory.CreateClient("API"); + + HttpRequestMessage requestMessage = new(HttpMethod.Post, $"pcrb/notify/responsiblePerson"); + + requestMessage.Content = new StringContent(JsonSerializer.Serialize(notification), + Encoding.UTF8, + "application/json"); + + HttpResponseMessage responseMessage = await httpClient.SendAsync(requestMessage); + + if (!responseMessage.IsSuccessStatusCode) + throw new Exception($"Unable to notify PCRB responsible person, because {responseMessage.ReasonPhrase}"); + } } diff --git a/MesaFabApproval.Shared/Models/MRB.cs b/MesaFabApproval.Shared/Models/MRB.cs index 8c3c4b8..1f68082 100644 --- a/MesaFabApproval.Shared/Models/MRB.cs +++ b/MesaFabApproval.Shared/Models/MRB.cs @@ -21,7 +21,7 @@ public class MRB { public DateTime ApprovalDate { get; set; } = DateTimeUtilities.MAX_DT; public string IssueDescription { get; set; } = ""; public int NumberOfLotsAffected { get; set; } - public int Val { get; set; } + public double Val { get; set; } public bool CustomerImpacted { get; set; } = false; public string CustomerImpactedName { get; set; } = ""; public string Department { get; set; } = ""; diff --git a/MesaFabApproval.Shared/Models/PCR3Document.cs b/MesaFabApproval.Shared/Models/PCR3Document.cs new file mode 100644 index 0000000..bd6dd85 --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCR3Document.cs @@ -0,0 +1,20 @@ +using MesaFabApproval.Shared.Utilities; + +namespace MesaFabApproval.Shared.Models; + +public class PCR3Document { + public int ID { get; set; } + public required int PlanNumber { get; set; } + public required string DocType { get; set; } + public string DocNumbers { get; set; } = "N/A"; + public DateTime CompletedDate { get; set; } = DateTimeUtilities.MAX_DT; + public int CompletedByID { get; set; } = 0; + public User? CompletedBy { get; set; } + public string Comment { get; set; } = string.Empty; + public int ECNNumber { get; set; } = 0; + + public string GetEcnNumberString() { + if (this.ECNNumber > 0) return this.ECNNumber.ToString(); + return string.Empty; + } +} diff --git a/MesaFabApproval.Shared/Models/PCRB.cs b/MesaFabApproval.Shared/Models/PCRB.cs index 0954cd4..da73f48 100644 --- a/MesaFabApproval.Shared/Models/PCRB.cs +++ b/MesaFabApproval.Shared/Models/PCRB.cs @@ -5,7 +5,6 @@ namespace MesaFabApproval.Shared.Models; public class PCRB { public static string[] Stages { get; } = { "Draft", - "QA Pre Approval", "PCR1", "PCR2", "PCR3", @@ -16,7 +15,7 @@ public class PCRB { public int OwnerID { get; set; } public string OwnerName { get; set; } = ""; public string Title { get; set; } = ""; - public string ChangeLevel { get; set; } = "Mesa"; + public string ChangeLevel { get; set; } = "Mesa - Class 3"; public bool IsITAR { get; set; } = false; public int CurrentStep { get; set; } = 0; public string ReasonForChange { get; set; } = ""; diff --git a/MesaFabApproval.Shared/Models/PCRBActionItem.cs b/MesaFabApproval.Shared/Models/PCRBActionItem.cs new file mode 100644 index 0000000..146f7c2 --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCRBActionItem.cs @@ -0,0 +1,21 @@ +using MesaFabApproval.Shared.Utilities; + +namespace MesaFabApproval.Shared.Models; + +public class PCRBActionItem { + public int ID { get; set; } + public required string Name { get; set; } + public bool Gating { get; set; } = false; + public bool ClosedStatus { get; set; } = false; + public DateTime? ClosedDate { get; set; } = DateTimeUtilities.MAX_DT; + public int ClosedByID { get; set; } = 0; + public User? ClosedBy { get; set; } + public required int UploadedByID { get; set; } + public User? UploadedBy { get; set; } + public DateTime UploadedDateTime { get; set; } = DateTime.Now; + public int ResponsiblePersonID { get; set; } = 0; + public User? ResponsiblePerson { get; set; } + public required int PlanNumber { get; set; } + public required int Step { get; set; } + public DateTime NotifyDate { get; set; } = DateTimeUtilities.MIN_DT; +} diff --git a/MesaFabApproval.Shared/Models/PCRBActionItemNotification.cs b/MesaFabApproval.Shared/Models/PCRBActionItemNotification.cs new file mode 100644 index 0000000..4b3daa3 --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCRBActionItemNotification.cs @@ -0,0 +1,7 @@ +namespace MesaFabApproval.Shared.Models; + +public class PCRBActionItemNotification { + public required PCRB PCRB { get; set; } + public required PCRBActionItem ActionItem { get; set; } + public required string Message { get; set; } +} diff --git a/MesaFabApproval.Shared/Models/PCRBAttachment.cs b/MesaFabApproval.Shared/Models/PCRBAttachment.cs new file mode 100644 index 0000000..f85a95e --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCRBAttachment.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Components.Forms; + +namespace MesaFabApproval.Shared.Models; + +public class PCRBAttachment { + public int ID { get; set; } + public required int PlanNumber { get; set; } + public required string FileName { get; set; } + public required int UploadedByID { get; set; } + public User? UploadedBy { get; set; } + public string Title { get; set; } = "NA"; + public required DateTime UploadDateTime { get; set; } + public string? Path { get; set; } + public IBrowserFile? File { get; set; } + public required int Step { get; set; } +} diff --git a/MesaFabApproval.Shared/Models/PCRBAttendee.cs b/MesaFabApproval.Shared/Models/PCRBAttendee.cs new file mode 100644 index 0000000..8c54ecf --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCRBAttendee.cs @@ -0,0 +1,12 @@ +namespace MesaFabApproval.Shared.Models; + +public class PCRBAttendee { + public int ID { get; set; } + public required int PlanNumber { get; set; } + public string JobTitle { get; set; } = ""; + public string Location { get; set; } = "Mesa"; + public bool Attended { get; set; } = false; + public required int AttendeeID { get; set; } + public User? Attendee { get; set; } + public required int Step { get; set; } +} diff --git a/MesaFabApproval.Shared/Models/PCRBNotification.cs b/MesaFabApproval.Shared/Models/PCRBNotification.cs new file mode 100644 index 0000000..c993511 --- /dev/null +++ b/MesaFabApproval.Shared/Models/PCRBNotification.cs @@ -0,0 +1,6 @@ +namespace MesaFabApproval.Shared.Models; + +public class PCRBNotification { + public required string Message { get; set; } + public required PCRB PCRB { get; set; } +} diff --git a/MesaFabApproval.Shared/Models/Process.cs b/MesaFabApproval.Shared/Models/Process.cs index 1c124bc..26297d5 100644 --- a/MesaFabApproval.Shared/Models/Process.cs +++ b/MesaFabApproval.Shared/Models/Process.cs @@ -3,7 +3,7 @@ public class Process { public required string Name { get; set; } = ""; - private static readonly Process Receiving = new Process { Name="Receiving" }; + private static readonly Process Receiving = new Process { Name = "Receiving" }; private static readonly Process Kitting = new Process { Name = "Kitting" }; private static readonly Process Cleans = new Process { Name = "Cleans" }; private static readonly Process Reactor = new Process { Name = "Reactor" }; @@ -15,8 +15,10 @@ public class Process { private static readonly Process Conversion = new Process { Name = "Conversion" }; private static readonly Process RMA = new Process { Name = "RMA" }; private static readonly Process CustomerCompliant = new Process { Name = "Customer Compliant" }; + private static readonly Process Nontransferrable = new Process { Name = "Nontransferrable" }; + private static readonly Process PartConversion = new Process { Name = "Part Conversion" }; - public static IEnumerable ProductionProcesses = new HashSet { + public static IEnumerable ProductionProcesses = new HashSet { Cleans, Reactor, Metrology, @@ -51,6 +53,8 @@ public class Process { public static IEnumerable QualityProcesses = new HashSet { RMA, - CustomerCompliant + CustomerCompliant, + Nontransferrable, + PartConversion }; } \ No newline at end of file diff --git a/MesaFabApproval.Shared/Models/SubRole.cs b/MesaFabApproval.Shared/Models/SubRole.cs index 6f6f96d..5ddcc68 100644 --- a/MesaFabApproval.Shared/Models/SubRole.cs +++ b/MesaFabApproval.Shared/Models/SubRole.cs @@ -1,9 +1,4 @@ -using System.Collections.Concurrent; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Serialization; - -namespace MesaFabApproval.Shared.Models; +namespace MesaFabApproval.Shared.Models; public class SubRole { public int SubRoleID { get; set; } diff --git a/MesaFabApproval.Shared/Services/MonInWorkerClient.cs b/MesaFabApproval.Shared/Services/MonInWorkerClient.cs index bdb57d5..3e7a935 100644 --- a/MesaFabApproval.Shared/Services/MonInWorkerClient.cs +++ b/MesaFabApproval.Shared/Services/MonInWorkerClient.cs @@ -53,7 +53,7 @@ public class MonInWorkerClient : IMonInWorkerClient { throw new ArgumentException("MonInBackoffSeconds environment variable not an integer"); _resource = Environment.GetEnvironmentVariable("FabApprovalApiMonInResource") ?? - throw new ArgumentNullException("OIWizardMonInResource environment variable not found"); + throw new ArgumentNullException("FabApprovalApiMonInResource environment variable not found"); } public async void PostStatus(string statusName, StatusValue statusValue) { diff --git a/MesaFabApproval.Shared/Utilities/DateTimeUtilities.cs b/MesaFabApproval.Shared/Utilities/DateTimeUtilities.cs index e42d604..dda2e05 100644 --- a/MesaFabApproval.Shared/Utilities/DateTimeUtilities.cs +++ b/MesaFabApproval.Shared/Utilities/DateTimeUtilities.cs @@ -8,7 +8,17 @@ public class DateTimeUtilities { return dt > MIN_DT ? dt.ToString("yyyy-MM-dd HH:mm") : ""; } + public static string GetDateAsStringMinDefault(DateTime? dt) { + DateTime copy = dt ?? MIN_DT; + return copy > MIN_DT ? copy.ToString("yyyy-MM-dd HH:mm") : ""; + } + public static string GetDateAsStringMaxDefault(DateTime dt) { return dt < MAX_DT ? dt.ToString("yyyy-MM-dd HH:mm") : ""; } + + public static string GetDateAsStringMaxDefault(DateTime? dt) { + DateTime copy = dt ?? MAX_DT; + return copy < MAX_DT ? copy.ToString("yyyy-MM-dd HH:mm") : ""; + } }