using System; using System.Collections.Generic; using System.Linq; #if !NET8 using System.Web; using System.Web.Mvc; #endif #if NET8 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; #endif using Fab2ApprovalSystem.DMO; using Fab2ApprovalSystem.Misc; using Fab2ApprovalSystem.Models; #if !NET8 using Kendo.Mvc.Extensions; using Kendo.Mvc.UI; #endif namespace Fab2ApprovalSystem.Controllers; [Authorize] #if !NET8 [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")] [SessionExpireFilter] #endif #if NET8 [Route("[controller]")] #endif public class PartsRequestController : Controller { private const int WorkflowNumber = 1; private readonly PartsRequestDMO prDMO = new(); private readonly UserAccountDMO userDMO = new(); private readonly WorkflowDMO wfDMO = new(); private readonly AppSettings? _AppSettings = GlobalVars.AppSettings; public PartsRequestController() { ViewBag.ShowReAssignApprovers = false; } protected ActionResult HandleValidationError(string msg) { Response.StatusCode = (int)System.Net.HttpStatusCode.BadRequest; return Json(new { result = "Invalid", detail = msg }); } protected ActionResult HandleAPIException(int issueID, Exception ex, string additionalKeys = "") { HandleException(issueID, ex, additionalKeys); Response.StatusCode = (int)System.Net.HttpStatusCode.InternalServerError; return Json(new { result = "Error", issueID = issueID, detail = ex.Message }); } protected void HandleException(int issueID, Exception ex, string additionalKeys = "") { System.Diagnostics.StackTrace st = new(); System.Diagnostics.StackFrame? sf = st.GetFrame(1); string controller = sf.GetMethod().DeclaringType.Name.Replace("Controller", ""); string method = sf.GetMethod().Name; string detailedException = string.Format( "Exception for issue # {0}\r\n" + "Controller: {1}, Method: {2}, User: {3}, Keys: {4}\r\n" + "=====\r\n", issueID, controller, method, User?.Identity?.Name, additionalKeys); Exception x = ex; while (x != null) { detailedException += x.ToString(); detailedException += "\r\n=====\r\n"; x = x.InnerException; } Functions.WriteEvent(_AppSettings, detailedException, System.Diagnostics.EventLogEntryType.Error); EventLogDMO.Add(new WinEventLog() { IssueID = issueID, UserID = GetUserIdentityName(), DocumentType = controller, OperationType = "Error", Comments = detailedException }); } public ActionResult Index() { return View(); } public ActionResult Edit(int issueID) { PartsRequest pr = new(); try { pr = prDMO.Get(issueID); if (pr == null) { ViewBag.ErrorDescription = "Document does not exist"; return View("Error"); } else if (pr.CurrentStep < 0) { ViewBag.ErrorDescription = "Document is deleted"; return View("Error"); } else if (pr.CurrentStep >= 1) { return RedirectToAction("EditApproval", new { issueID = issueID }); } else { if (pr.OriginatorID != GlobalVars.GetUserId(GetSession())) { if (!GlobalVars.IsAdmin(GetSession())) { return RedirectToAction("ReadOnly", new { issueID = issueID }); } } ViewBag.UserList = userDMO.GetAllUsers(); return View(pr); } } catch (Exception e) { HandleException(issueID, e); return View("Error"); } } #if !NET8 [HttpPost] public ActionResult Edit(PartsRequest pr) { try { var pr_srv = prDMO.Get(pr.PRNumber); if (pr_srv == null) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document does not exist"); } if (pr_srv.CurrentStep < 0) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document is deleted"); } if (pr_srv.CurrentStep >= 1) { return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Parts Request is not editable"); } prDMO.Update(pr); return Content(""); } catch (Exception e) { return HandleAPIException(pr.PRNumber, e); } } #endif public ActionResult EditApproval(int issueID) { PartsRequest pr = new(); try { int myUserID = GlobalVars.GetUserId(GetSession()); pr = prDMO.Get(issueID); if (pr == null) { ViewBag.ErrorDescription = "Document does not exist"; return View("Error"); } if (pr.CurrentStep < 0) { ViewBag.ErrorDescription = "Document is deleted"; return View("Error"); } var wfStep = wfDMO.GetWorkflowStep((int)GlobalVars.DocumentType.PartsRequest, WorkflowNumber, pr.CurrentStep); var userList = MiscDMO.GetPendingApproversListByDocument(issueID, Convert.ToByte(pr.CurrentStep), (int)GlobalVars.DocumentType.PartsRequest); ViewBag.IsApprover = (userList.Count(u => u.UserID == myUserID) > 0); if (ViewBag.IsApprover == false) { if (pr.OriginatorID != myUserID) { if (!GlobalVars.IsAdmin(GetSession())) { return RedirectToAction("ReadOnly", new { issueID = issueID }); } } } ViewBag.IsAdmin = GlobalVars.IsAdmin(GetSession()); ViewBag.IsOriginator = (pr.OriginatorID == myUserID); ViewBag.AllowReject = (wfStep != null && (wfStep.AllowReject.HasValue && wfStep.AllowReject.Value)); ViewBag.ShowReAssignApprovers = ViewBag.IsAdmin || ViewBag.IsOriginator; ViewBag.ShowAddApprovers = ViewBag.IsAdmin || ViewBag.IsApprover || ViewBag.IsOriginator; ViewBag.UserList = userDMO.GetAllUsers(); return View(pr); } catch (Exception e) { HandleException(issueID, e); return View("Error"); } } public ActionResult ReadOnly(int issueID) { PartsRequest pr = new(); try { int myUserID = GlobalVars.GetUserId(GetSession()); pr = prDMO.Get(issueID); if (pr == null) { ViewBag.ErrorDescription = "Document does not exist"; return View("Error"); } if (pr.CurrentStep < 0) { ViewBag.ErrorDescription = "Document is deleted"; return View("Error"); } ViewBag.IsAdmin = GlobalVars.IsAdmin(GetSession()); ViewBag.IsOriginator = (pr.OriginatorID == myUserID); ViewBag.UserList = userDMO.GetAllUsers(); return View(pr); } catch (Exception e) { HandleException(issueID, e); return View("Error"); } } public ActionResult Create() { PartsRequest pr = new(); try { pr.OriginatorID = GlobalVars.GetUserId(GetSession()); if (!CanCreatePartsRequest(pr.OriginatorID) && !GlobalVars.GetCanCreatePartsRequest(GetSession())) throw new Exception("User does not have permission to create Parts Request"); prDMO.Insert(pr); return RedirectToAction("Edit", new { issueID = pr.PRNumber }); } catch (Exception e) { return HandleAPIException(pr.PRNumber, e); } } public static bool CanCreatePartsRequest(int userID) { AdminDMO adminDMO = new(); Role? role = adminDMO.GetSubRoles().FirstOrDefault(r => string.Equals(r.RoleName, "Parts Request", StringComparison.OrdinalIgnoreCase)); if (role != null) { var subrole = role.SubRoles.FirstOrDefault(sr => string.Equals(sr.SubRoleCategoryItem, "Originator", StringComparison.OrdinalIgnoreCase)); if (subrole != null) { var users = adminDMO.GetAllUsersBySubRole(subrole.SubRoleID); if (users.Count(u => u.UserID == userID) > 0) { return true; } } } return false; } #if !NET8 public ActionResult Attachment_Read([DataSourceRequest] DataSourceRequest request, int prNumber) { try { return GetJsonResult(prDMO.GetAttachments(prNumber).ToDataSourceResult(request)); } catch (Exception ex) { return HandleAPIException(prNumber, ex); } } [HttpPost] public ActionResult AttachSave(IEnumerable files, int prNumber) { // The Name of the Upload component is "files" if (files != null) { int userId = GlobalVars.GetUserId(GetSession()); foreach (var file in files) { PartsRequestHelper.AttachSave(_AppSettings, prDMO, prNumber, userId, file.FileName, file.InputStream); } } return Content(""); } #endif public FileResult DownloadFile(string attachmentID, string prNumber) { string fileName = prDMO.GetFileName(attachmentID); string folderPath = _AppSettings.AttachmentFolder + "PartsRequest\\" + prNumber.ToString(); var sDocument = System.IO.Path.Combine(folderPath, fileName); var FDir_AppData = _AppSettings.AttachmentFolder; if (!sDocument.StartsWith(FDir_AppData)) { // Ensure that we are serving file only inside the Fab2ApprovalAttachments folder // and block requests outside like "../web.config" throw new HttpException(403, "Forbidden"); } if (!System.IO.File.Exists(sDocument)) return null; return File(sDocument, System.Net.Mime.MediaTypeNames.Application.Octet, fileName); } [HttpPost] public ActionResult DeleteAttachment(int attachmentID, string fileName, int prNumber) { try { if (ModelState.IsValid) { prDMO.DeleteAttachment(attachmentID); var physicalPath = System.IO.Path.Combine(_AppSettings.AttachmentFolder + @"PartsRequest\" + prNumber.ToString(), fileName); if (System.IO.File.Exists(physicalPath)) { System.IO.File.Delete(physicalPath); } } return Content(""); } catch (Exception e) { return HandleAPIException(prNumber, e, "AttachmentID=" + attachmentID.ToString()); } } #if !NET8 [HttpPost] public ActionResult Submit(int prNumber) { try { int myUserID = GlobalVars.GetUserId(GetSession()); var pr = prDMO.Get(prNumber); if (pr == null) return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document does not exist"); if (string.IsNullOrWhiteSpace(pr.Title)) return HandleValidationError("Title is required"); if (pr.RequestorID <= 0) return HandleValidationError("Requestor is required"); if (pr.TechLeadID <= 0) return HandleValidationError("Tech Lead is required"); prDMO.Submit(prNumber, myUserID); var approvers = MiscDMO.GetApprovalsByDocument(prNumber, (int)GlobalVars.DocumentType.PartsRequest); if (approvers.Count(a => a.Step.HasValue && a.Step.Value == 1 && a.UserID == myUserID) > 0) { // Auto-Approve if the user is an approver for workflow step 1 var c = Approve(prNumber, 1, "Auto-Approve"); if (c != null && c is ContentResult contentResult) { var result = contentResult.Content; if (!string.Equals(result, "OK")) throw new Exception(result); } if (c != null && c is JsonResult) return c; } else { // Do step 1 notification NotifyApprovers(prNumber, 1); } if (IsAjaxRequest()) { return Content("Redirect"); } else { return Content("Invalid"); } } catch (Exception e) { return HandleAPIException(prNumber, e); } } public ActionResult GetApproversList([DataSourceRequest] DataSourceRequest request, int issueID, byte step) { try { return Json(MiscDMO.GetApproversListByDocument(issueID, step, (int)GlobalVars.DocumentType.PartsRequest).ToDataSourceResult(request)); } catch (Exception e) { return HandleAPIException(issueID, e, "Step=" + step.ToString()); } } public ActionResult GetAllUsersList() { try { UserAccountDMO userDMO = new UserAccountDMO(); IEnumerable userlist = userDMO.GetAllUsers(); return GetJsonResult(userlist); } catch (Exception e) { return HandleAPIException(0, e); } } #endif [HttpPost] public ActionResult ReAssignApproval(int issueID, int fromUserID, int userIDs, byte step) { try { string email = wfDMO.ReAssignApproval( issueID, fromUserID, userIDs, step, (int)GlobalVars.DocumentType.PartsRequest); NotifyReAssignment(issueID, email); return Content("OK"); } catch (Exception e) { return HandleAPIException(issueID, e); } } #if !NET8 [HttpPost] public ActionResult Approve(int prNumber, byte currentStep, string comments) { try { bool lastStep = false; bool lastApproverInCurrentStep = false; int myUserID = GlobalVars.GetUserId(GetSession()); var pr = prDMO.Get(prNumber); if (pr == null) return new HttpStatusCodeResult(System.Net.HttpStatusCode.BadRequest, "Document does not exist"); while (true) { lastApproverInCurrentStep = wfDMO.Approve( _AppSettings, prNumber, currentStep, comments, out lastStep, GlobalVars.GetUserId(GetSession()), (int)GlobalVars.DocumentType.PartsRequest, WorkflowNumber); if (!lastApproverInCurrentStep) break; if (lastStep) { NotifyCompletion(prNumber); break; } currentStep++; var approvers = MiscDMO.GetApprovalsByDocument(prNumber, (int)GlobalVars.DocumentType.PartsRequest); if (approvers.Count(a => a.Step.HasValue && a.Step.Value == currentStep) == 0) return Content("No approvers found for next step, contact support!"); // only continue with approving if the next step has me as an approver also if (approvers.Count(a => a.Step.HasValue && a.Step.Value == currentStep && a.UserID == myUserID) == 0) { NotifyApprovers(prNumber, currentStep); break; } } return Content("OK"); } catch (Exception e) { return HandleAPIException(prNumber, e); } } #endif protected void NotifyReAssignment(int prNumber, string email) { var pr = prDMO.Get(prNumber); if (pr == null) return; string username = GlobalVars.GetUserName(GetSession()); PartsRequestHelper.SendEmailNotification(_AppSettings, username, subject: string.Format("Parts Request Re-Assignment notice for # {0} - {1}", pr.PRNumber, pr.Title), prNumber: prNumber, toEmail: email, emailTemplate: "PRReAssigned.txt"); EventLogDMO.Add(new WinEventLog() { IssueID = prNumber, UserID = GetUserIdentityName(), DocumentType = "PR", OperationType = "Email", Comments = "ReAssigned Approver: " + email }); } protected void NotifyCompletion(int prNumber) { var pr = prDMO.Get(prNumber); if (pr == null) return; var u = userDMO.GetUserByID(pr.RequestorID); if ((u != null) && (!string.IsNullOrWhiteSpace(u.Email))) { string username = GlobalVars.GetUserName(GetSession()); PartsRequestHelper.SendEmailNotification(_AppSettings, username, subject: string.Format("Parts Request Completion notice for # {0} - {1}", pr.PRNumber, pr.Title), prNumber: prNumber, toEmail: u.Email, emailTemplate: "PRCompleted.txt"); EventLogDMO.Add(new WinEventLog() { IssueID = prNumber, UserID = GetUserIdentityName(), DocumentType = "PR", OperationType = "Email", Comments = "Completed: " + u.Email }); } } public void NotifyRejection(int prNumber) { var pr = prDMO.Get(prNumber); if (pr == null) return; var u = userDMO.GetUserByID(pr.OriginatorID); if ((u != null) && (!string.IsNullOrWhiteSpace(u.Email))) { string username = GlobalVars.GetUserName(GetSession()); PartsRequestHelper.SendEmailNotification(_AppSettings, username, subject: string.Format("Parts Request Rejection notice for # {0} - {1}", pr.PRNumber, pr.Title), prNumber: prNumber, toEmail: u.Email, emailTemplate: "PRReject.txt"); EventLogDMO.Add(new WinEventLog() { IssueID = prNumber, UserID = GetUserIdentityName(), DocumentType = "PR", OperationType = "Email", Comments = "Rejected: " + u.Email }); } } protected void NotifyApprovers(int prNumber, byte step) { try { string emailSentList = ""; var pr = prDMO.Get(prNumber); if (pr == null) throw new Exception("Invalid pr#"); List emailList = MiscDMO.GetApproverEmailListByDocument( prNumber, step, (int)GlobalVars.DocumentType.PartsRequest).Distinct().ToList(); foreach (string email in emailList) { try { string username = GlobalVars.GetUserName(GetSession()); PartsRequestHelper.SendEmailNotification(_AppSettings, username, subject: string.Format("Parts Request Assignment notice for # {0} - {1}", pr.PRNumber, pr.Title), prNumber: prNumber, toEmail: email, emailTemplate: "PRAssigned.txt"); } catch (Exception ex) { HandleException(prNumber, ex, "email=" + email); } emailSentList += email + ","; } try { EventLogDMO.Add(new WinEventLog() { IssueID = prNumber, UserID = GetUserIdentityName(), DocumentType = "PR", OperationType = "Email", Comments = "Approvers for Step " + step.ToString() + ":" + emailSentList }); } catch { } } catch (Exception e) { HandleException(prNumber, e, "Step=" + step.ToString()); } } [HttpPost] public ActionResult Reject(int prNumber, byte currentStep, string comments) { try { if (GlobalVars.IsUserIdValueNotNull(GetSession())) { wfDMO.Reject(prNumber, currentStep, comments, GlobalVars.GetUserId(GetSession()), (int)GlobalVars.DocumentType.PartsRequest); NotifyRejection(prNumber); } else { Response.Redirect("~/Account/Login"); } return Content("OK"); } catch (Exception e) { return HandleAPIException(prNumber, e); } } #if !NET8 public ActionResult ApprovalLogHistory_Read([DataSourceRequest] DataSourceRequest request, int prNumber) { return GetJsonResult(prDMO.GetApprovalLogHistory(prNumber).ToDataSourceResult(request)); } #endif protected void NotifyAssignment(int prNumber, string email) { var pr = prDMO.Get(prNumber); if (pr == null) return; string username = GlobalVars.GetUserName(GetSession()); PartsRequestHelper.SendEmailNotification(_AppSettings, username, subject: string.Format("Parts Request Assignment notice for # {0} - {1}", pr.PRNumber, pr.Title), prNumber: prNumber, toEmail: email, emailTemplate: "PRAssigned.txt"); EventLogDMO.Add(new WinEventLog() { IssueID = prNumber, UserID = GetUserIdentityName(), DocumentType = "PR", OperationType = "Email", Comments = "Assigned Approver: " + email }); } [HttpPost] public void AddAdditionalApproval(int issueID, byte step, string userIDs) { var emailArray = ""; try { emailArray = wfDMO.AddAdditionalApproval(issueID, userIDs, step, (int)GlobalVars.DocumentType.PartsRequest); } catch (Exception e) { HandleAPIException(issueID, e); } string emailSentList = ""; string[] emaiList = emailArray.Split(new char[] { '~' }); foreach (string email in emaiList) { if (email.Length > 0) { NotifyAssignment(issueID, email); emailSentList += email + ","; } } try { EventLogDMO.Add(new WinEventLog() { IssueID = issueID, UserID = GetUserIdentityName(), DocumentType = "PR", OperationType = "Email", Comments = "Additional Approver: " + emailSentList }); } catch { } } #if !NET8 private System.Web.HttpSessionStateBase GetSession() => Session; private JsonResult GetJsonResult(object? data) => Json(data, JsonRequestBehavior.AllowGet); private bool IsAjaxRequest() => Request.IsAjaxRequest(); #endif #if NET8 private Microsoft.AspNetCore.Http.ISession GetSession() => HttpContext.Session; private JsonResult GetJsonResult(object? data) => Json(data); private bool IsAjaxRequest() => Request.Headers.TryGetValue("X-Requested-With", out Microsoft.Extensions.Primitives.StringValues strings) && strings[0] == "XMLHttpRequest"; #endif private string GetUserIdentityName() => @User.Identity.Name; }