2025-05-28 13:34:48 -07:00

666 lines
23 KiB
C#

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<HttpPostedFileBase> 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<LoginModel> 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<string> 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;
}