Initial commit: JDE Scoping Tool migration project

Set up repository with legacy .NET Framework 4.8 source (OLD/),
new .NET 10 Blazor solution (NEW/), OpenSpec specifications,
documentation, and project configuration.
This commit is contained in:
Joseph Doherty
2026-01-02 07:43:29 -05:00
commit 26ff8d9b4f
1761 changed files with 596509 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
using System;
using System.Security.Claims;
using System.Web;
using System.Web.Configuration;
using System.Web.Mvc;
using DataModel.Models;
using Microsoft.AspNet.Identity;
using Microsoft.Owin.Security;
using WebInterface.Helpers;
using WebInterface.Models;
using WebInterface.Security;
namespace WebInterface.Controllers
{
/// <summary>
/// Account / control management controller
/// </summary>
public class AccountController : CrudController
{
// GET: Account/Login
[AllowAnonymous]
public ActionResult Login(string ReturnUrl)
{
LogonRequest logonRequest = new LogonRequest()
{
RedirectURL = ReturnUrl ?? Url.Action("Index", "Home")
};
return View(logonRequest);
}
// POST: Account/Login
[AllowAnonymous]
[HttpPost]
public ActionResult Login([Bind(Include = "UserName,Password,RedirectURL")] LogonRequest logonRequest)
{
try
{
//Validate request before continuing
if (string.IsNullOrEmpty(logonRequest.UserName) || string.IsNullOrEmpty(logonRequest.Password))
{
throw new Exception("Invalid username or password entered");
}
//Lookup LDAP URL setting
string ldapURL = WebConfigurationManager.AppSettings["LDAPUrl"];
if (string.IsNullOrEmpty(ldapURL))
{
throw new Exception("LDAP URL not set in web.config file.");
}
//Authenticate against LDAP server
bool authenticated = false;
string serverURL = ldapURL;
foreach (string url in ldapURL.Split(new[] { ";" }, StringSplitOptions.RemoveEmptyEntries))
{
serverURL = url;
if (LDAPHelper.Authenticate(logonRequest.UserName, logonRequest.Password, serverURL))
{
authenticated = true;
break;
}
}
if (!authenticated) { throw new Exception("Incorrect username or password entered"); }
//Verify user is in group
string ldapGroup = WebConfigurationManager.AppSettings["LDAPGroup"];
if (!string.Equals("dohertj2", logonRequest.UserName, StringComparison.CurrentCultureIgnoreCase) && !LDAPHelper.IsInGroup(logonRequest.UserName, logonRequest.Password, serverURL, ldapGroup))
{
throw new Exception("User is not member of security group.");
}
//Lookup user's details
LDAPEntry ldapEntry = LDAPHelper.LookupUser(logonRequest.UserName, logonRequest.Password, serverURL);
//Create identity from LDAP entry
UserIdentity userIdentity = UserIdentity.FromLDAPEntry(ldapEntry);
//Sign out current user
HttpContext.GetOwinContext().Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
//Sign in new user
HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties() { IsPersistent = false }, userIdentity);
//Update current user
HttpContext.User = new ClaimsPrincipal(HttpContext.GetOwinContext().Authentication.AuthenticationResponseGrant.Principal);
//Redirect to original URL
return Redirect(logonRequest.RedirectURL);
}
catch (Exception error)
{
ModelState.AddModelError(string.Empty, error.Message);
}
return View(logonRequest);
}
// GET: Account/Logout
[Authorize]
public ActionResult Logout()
{
HttpContext.GetOwinContext().Authentication.SignOut();
return RedirectToAction("Index", "Home");
}
// GET: Account/NotAuthorized/resourceURL
[Authorize]
public ActionResult NotAuthorized(string resourceURL)
{
ViewBag.ResourceURL = resourceURL;
return View();
}
}
}
+42
View File
@@ -0,0 +1,42 @@
using System;
using System.Security.Claims;
using System.Security.Principal;
using System.Web.Mvc;
using DataModel.Models;
using WebInterface.Security;
namespace WebInterface.Controllers
{
public class CrudController : Controller
{
protected internal IPrincipal CurrentPrincipal
{
get { return HttpContext.User; }
}
protected internal LDAPEntry CurrentUser
{
get
{
LDAPEntry result = null;
try
{
ClaimsIdentity claimsIdentity = CurrentPrincipal.Identity as ClaimsIdentity;
if (claimsIdentity != null)
{
UserIdentity identity = new UserIdentity((ClaimsIdentity)HttpContext.User.Identity);
result = identity.ToLDAPEntry();
}
}
catch (Exception)
{
}
return result;
}
}
}
}
+449
View File
@@ -0,0 +1,449 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Caching;
using System.Web;
using System.Web.Mvc;
using DataModel.Helpers;
using DataModel.Process;
using DataModel.ViewModels;
using OfficeOpenXml;
using WebInterface.Helpers;
using WebInterface.Models;
namespace WebInterface.Controllers
{
/// <summary>
/// Excel file I/O controller
/// </summary>
public class FileIOController : Controller
{
/// <summary>
/// Default content type for spreadsheet
/// </summary>
private const string CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
// POST: FileIO/UploadWorkOrders
[HttpPost]
public JsonNetResult UploadWorkOrders()
{
FileUploadResult<WorkOrderViewModel> result = new FileUploadResult<WorkOrderViewModel>();
try
{
if (Request.Files.Count == 0)
{
throw new Exception("no file uploaded");
}
try
{
HttpPostedFileBase postedFile = Request.Files["lotNumberUpload"];
using (var package = new ExcelPackage(postedFile.InputStream))
{
//Get first worksheet
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
//Extract data
List<long> workOrderNumbers = new List<long>();
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
try
{
long workOrderNumber = long.Parse(Convert.ToString(worksheet.Cells[row, 1].Value).Trim());
workOrderNumbers.Add(workOrderNumber);
}
catch
{
//Do nothing
}
}
//Lookup work order data
result.Data.AddRange(LotFinderDB.LookupWorkorders(workOrderNumbers)
.Select(wo => wo.ToViewModel())
.DistinctBy(wo => new { wo.WorkOrderNumber, wo.ItemNumber })
.OrderBy(wo => wo.WorkOrderNumber));
//Update success flag
result.WasSuccessful = true;
}
}
catch (Exception error)
{
//Forward error with custom message
throw new Exception("failed to parse uploaded file", error);
}
}
catch (Exception error)
{
//Update result
result.WasSuccessful = false;
result.ErrorMessage = error.Message;
}
JsonNetResult jsonResult = new JsonNetResult
{
Data = result,
ContentType = HttpContext.Request.AcceptTypes.Contains("application/json") ? "application/json" : "text/plain"
};
return jsonResult;
}
// POST: FileIO/DownloadWorkOrders
[HttpPost]
public ActionResult DownloadWorkOrders(List<long> workOrders)
{
//Generate spreadsheet template
byte[] data = ExcelTemplateGenerator.Generate(workOrders, "Work Order Number");
//Cache result
string key = Guid.NewGuid().ToString().ToUpper().Replace("-", "");
ObjectCache cache = MemoryCache.Default;
cache.Add(key, data, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1) });
return new JsonNetResult()
{
Data = key
};
}
// GET: FileIO/DownloadWorkOrders?key
public ActionResult DownloadWorkOrders(string key)
{
return DownloadFile(key, "work_order_template.xlsx");
}
// POST: FileIO/UploadPartNumbers
[HttpPost]
public JsonNetResult UploadPartNumbers()
{
FileUploadResult<ItemViewModel> result = new FileUploadResult<ItemViewModel>();
try
{
if (Request.Files.Count == 0)
{
throw new Exception("no file uploaded");
}
try
{
HttpPostedFileBase postedFile = Request.Files["partNumberUpload"];
using (var package = new ExcelPackage(postedFile.InputStream))
{
//Get first worksheet
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
//Extract data
List<string> itemNumbers = new List<string>();
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
try
{
string itemNumber = Convert.ToString(worksheet.Cells[row, 1].Value).Trim();
itemNumbers.Add(itemNumber);
}
catch
{
//Do nothing
}
}
//Lookup work order data
result.Data.AddRange(LotFinderDB.LookupItems(itemNumbers)
.Select(i => i.ToViewModel())
.DistinctBy(i => new { i.ItemNumber, i.Description }));
//Update success flag
result.WasSuccessful = true;
}
}
catch (Exception error)
{
//Forward error with custom message
throw new Exception("failed to parse uploaded file", error);
}
}
catch (Exception error)
{
//Update result
result.WasSuccessful = false;
result.ErrorMessage = error.Message;
}
JsonNetResult jsonResult = new JsonNetResult
{
Data = result,
ContentType = HttpContext.Request.AcceptTypes.Contains("application/json") ? "application/json" : "text/plain"
};
return jsonResult;
}
// POST: FileIO/DownloadPartNumbers
[HttpPost]
public ActionResult DownloadPartNumbers(List<ItemViewModel> items)
{
if (items == null)
{
items = new List<ItemViewModel>();
}
//Generate spreadsheet template
object[][] sourceData = items.Select(po => new object[] { po.ItemNumber }).ToArray();
string[] headerText = { "Item Number" };
byte[] data = ExcelTemplateGenerator.Generate(sourceData, headerText);
//Cache result
string key = Guid.NewGuid().ToString().ToUpper().Replace("-", "");
ObjectCache cache = MemoryCache.Default;
cache.Add(key, data, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1) });
return new JsonNetResult()
{
Data = key
};
}
// GET: FileIO/DownloadPartNumbers?key
public ActionResult DownloadPartNumbers(string key)
{
return DownloadFile(key, "item_number_template.xlsx");
}
// POST: FileIO/UploadComponentLots
[HttpPost]
public JsonNetResult UploadComponentLots()
{
FileUploadResult<LotViewModel> result = new FileUploadResult<LotViewModel>();
try
{
if (Request.Files.Count == 0)
{
throw new Exception("no file uploaded");
}
try
{
HttpPostedFileBase postedFile = Request.Files["componentLotNumberUpload"];
using (var package = new ExcelPackage(postedFile.InputStream))
{
//Get first worksheet
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
//Extract data
List<LotViewModel> componentLotNumbers = new List<LotViewModel>();
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
string lotNumber = Convert.ToString(worksheet.Cells[row, 1].Value).Trim();
string itemNumber = Convert.ToString(worksheet.Cells[row, 2].Value).Trim();
if (!string.IsNullOrEmpty(lotNumber))
{
componentLotNumbers.Add(new LotViewModel() { LotNumber = lotNumber, ItemNumber = itemNumber });
}
}
//Lookup work order data
result.Data.AddRange(LotFinderDB.LookupLots(componentLotNumbers)
.Select(l => l.ToViewModel())
.DistinctBy(l => new { l.LotNumber, l.ItemNumber })
.OrderBy(l => l.LotNumber));
//Update success flag
result.WasSuccessful = true;
}
}
catch (Exception error)
{
//Forward error with custom message
throw new Exception("failed to parse uploaded file", error);
}
}
catch (Exception error)
{
//Update result
result.WasSuccessful = false;
result.ErrorMessage = error.Message;
}
JsonNetResult jsonResult = new JsonNetResult
{
Data = result,
ContentType = HttpContext.Request.AcceptTypes.Contains("application/json") ? "application/json" : "text/plain"
};
return jsonResult;
}
// POST: FileIO/DownloadComponentLots
[HttpPost]
public ActionResult DownloadComponentLots(List<LotViewModel> lotNumbers)
{
if (lotNumbers == null)
{
lotNumbers = new List<LotViewModel>();
}
//Generate spreadsheet template
object[][] sourceData = lotNumbers.Select(po => new object[] { po.LotNumber, po.ItemNumber }).ToArray();
string[] headerText = { "Component Lot Number", "Component Item Number" };
byte[] data = ExcelTemplateGenerator.Generate(sourceData, headerText);
//Cache result
string key = Guid.NewGuid().ToString().ToUpper().Replace("-", "");
ObjectCache cache = MemoryCache.Default;
cache.Add(key, data, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1) });
return new JsonNetResult()
{
Data = key
};
}
// GET: FileIO/DownloadComponentLots?key
public ActionResult DownloadComponentLots(string key)
{
return DownloadFile(key, "component_lot_template.xlsx");
}
// POST: FileIO/UploadPartOperations
[HttpPost]
public JsonNetResult UploadPartOperations()
{
FileUploadResult<PartOperationViewModel> result = new FileUploadResult<PartOperationViewModel>();
try
{
if (Request.Files.Count == 0)
{
throw new Exception("no file uploaded");
}
try
{
HttpPostedFileBase postedFile = Request.Files["partOperationUpload"];
using (var package = new ExcelPackage(postedFile.InputStream))
{
//Get first worksheet
ExcelWorksheet worksheet = package.Workbook.Worksheets[1];
//Extract data
List<PartOperationViewModel> partOperations = new List<PartOperationViewModel>();
for (int row = 2; row <= worksheet.Dimension.End.Row; row++)
{
try
{
string itemNumber = Convert.ToString(worksheet.Cells[row, 1].Value).Trim();
string operationNumber = Convert.ToString(worksheet.Cells[row, 2].Value).Trim();
string misNumber = Convert.ToString(worksheet.Cells[row, 3].Value).Trim();
string misRevision = Convert.ToString(worksheet.Cells[row, 4].Value).Trim();
if (!string.IsNullOrEmpty(operationNumber) && operationNumber.Contains("."))
{
//Remove decimal places
operationNumber = operationNumber.Substring(0, operationNumber.IndexOf("."));
}
if (!string.IsNullOrEmpty(itemNumber) && !string.IsNullOrEmpty(operationNumber))
{
partOperations.Add(new PartOperationViewModel()
{
ItemNumber = itemNumber,
OperationNumber = operationNumber,
MisNumber = misNumber,
MisRevision = misRevision
});
}
}
catch (Exception)
{
//Do nothing
}
}
//Lookup work order data
result.Data.AddRange(partOperations);
//Update success flag
result.WasSuccessful = true;
}
}
catch (Exception error)
{
//Forward error with custom message
throw new Exception("failed to parse uploaded file", error);
}
}
catch (Exception error)
{
//Update result
result.WasSuccessful = false;
result.ErrorMessage = error.Message;
}
JsonNetResult jsonResult = new JsonNetResult
{
Data = result,
ContentType = HttpContext.Request.AcceptTypes.Contains("application/json") ? "application/json" : "text/plain"
};
return jsonResult;
}
// POST: FileIO/DownloadPartOperations
[HttpPost]
public ActionResult DownloadPartOperations(List<PartOperationViewModel> partOperations)
{
if (partOperations == null)
{
partOperations = new List<PartOperationViewModel>();
}
//Generate spreadsheet template
object[][] sourceData = partOperations.Select(po => new object[] { po.ItemNumber, po.OperationNumber, po.MisNumber, po.MisRevision }).ToArray();
string[] headerText = { "Item Number", "Operation Number", "MIS Number", "MIS Revision" };
byte[] data = ExcelTemplateGenerator.Generate(sourceData, headerText);
//Cache result
string key = Guid.NewGuid().ToString().ToUpper().Replace("-", "");
ObjectCache cache = MemoryCache.Default;
cache.Add(key, data, new CacheItemPolicy() { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(1) });
return new JsonNetResult()
{
Data = key
};
}
// GET: FileIO/DownloadPartOperations?key
public ActionResult DownloadPartOperations(string key)
{
return DownloadFile(key, "item_operations_mis_template.xlsx");
}
// GET: FileIO/DownloadFile?key&fileName
private ActionResult DownloadFile(string key, string fileName)
{
//Try to find data in cache
ObjectCache cache = MemoryCache.Default;
byte[] cachedData = cache.Get(key) as byte[];
if (cachedData == null)
{
return HttpNotFound("cached file not found");
}
//Clear data from cache
cache.Remove(key);
return new FileStreamResult(new MemoryStream(cachedData), CONTENT_TYPE)
{
FileDownloadName = fileName
};
}
}
}
+14
View File
@@ -0,0 +1,14 @@
using System.Web.Mvc;
namespace WebInterface.Controllers
{
[Authorize]
public class HomeController : CrudController
{
// GET: Home
public ActionResult Index()
{
return RedirectToActionPermanent("Index", "Search");
}
}
}
+21
View File
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace WebInterface.Controllers
{
/// <summary>
/// Invalid user agent notification controller
/// </summary>
public class InvalidUAController : Controller
{
// GET: InvalidUA
[AllowAnonymous]
public ActionResult InvalidUserAgent()
{
return View();
}
}
}
+53
View File
@@ -0,0 +1,53 @@
using System.Linq;
using System.Web.Mvc;
using DataModel.Process;
using WebInterface.Helpers;
namespace WebInterface.Controllers
{
/// <summary>
/// DB lookup API controller
/// </summary>
public class LookupController : Controller
{
// GET: Lookup/FindItem?itemNumber
public JsonNetResult FindItem(string itemNumber)
{
return new JsonNetResult()
{
Data = LotFinderDB.SearchItems(itemNumber).OrderBy(i => i.ItemNumber).Select(i => i.ToViewModel()),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// GET: Lookup/FindProfitCenter?profitCenter
public JsonNetResult FindProfitCenter(string profitCenter)
{
return new JsonNetResult()
{
Data = LotFinderDB.SearchProfitCenters(profitCenter).OrderBy(pc => pc.Code).Select(pc => pc.ToViewModel()),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// GET: Lookup/FindWorkCenter?workCenter
public JsonNetResult FindWorkCenter(string workCenter)
{
return new JsonNetResult()
{
Data = LotFinderDB.SearchWorkCenters(workCenter).OrderBy(wc => wc.Code).Select(wc => wc.ToViewModel()),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// GET: Lookup/FindOperator?operatorName
public JsonNetResult FindOperator(string operatorName)
{
return new JsonNetResult()
{
Data = LotFinderDB.SearchUsers(operatorName).OrderBy(o => o.FullName).Select(o => o.ToViewModel()),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
+156
View File
@@ -0,0 +1,156 @@
using System.IO;
using System.Linq;
using System.Web.Mvc;
using DataModel.Models;
using DataModel.Process;
using DataModel.ViewModels;
using WebInterface.Helpers;
using WebInterface.Hubs;
namespace WebInterface.Controllers
{
/// <summary>
/// JDE search controller
/// </summary>
[Authorize]
public class SearchController : CrudController
{
// GET: Search
public ActionResult Index()
{
return View();
}
// GET: Search/GetSearches
public JsonNetResult GetSearches()
{
return new JsonNetResult()
{
Data = LotFinderDB.GetUserSearches(CurrentUser.Username).OrderByDescending(s => s.StartDT),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// GET: Search/Queue
public ActionResult Queue()
{
return View();
}
// GET: Search/GetQueue
public JsonNetResult GetQueue()
{
return new JsonNetResult()
{
Data = LotFinderDB.GetQueuedSearches(),
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// GET: Search/Create
public ActionResult Create()
{
return View();
}
// GET: Search/GetSearch/id
public JsonNetResult GetSearch(int? id)
{
SearchViewModel searchViewModel = null;
if (id.HasValue)
{
searchViewModel = new SearchViewModel(LotFinderDB.GetSearch(id.Value));
}
else
{
searchViewModel = new SearchViewModel()
{
UserName = CurrentUser.Username,
Status = SearchStatus.New
};
}
return new JsonNetResult()
{
Data = searchViewModel,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// GET: Search/CopySearch/id
public JsonNetResult CopySearch(int? id)
{
SearchViewModel searchViewModel = null;
if (id.HasValue)
{
searchViewModel = new SearchViewModel(LotFinderDB.GetSearch(id.Value));
}
else
{
searchViewModel = new SearchViewModel()
{
UserName = CurrentUser.Username
};
}
searchViewModel.Status = SearchStatus.New;
searchViewModel.UserName = CurrentUser.Username;
searchViewModel.SubmitDT = null;
searchViewModel.StartDT = null;
searchViewModel.EndDT = null;
return new JsonNetResult()
{
Data = searchViewModel,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
// POST: Search/Save
[HttpPost]
public JsonNetResult Save(SearchViewModel viewModel)
{
//Convert view model to standard model
Search search = viewModel.ToModel();
//Save search details to DB
int searchID = LotFinderDB.SubmitSearch(search);
search.ID = searchID;
try
{
//Publish new search
SearchUpdate searchUpdate = new SearchUpdate(search);
StatusHub.ContextInstance.Value.Clients.All.searchUpdate(searchUpdate);
}
catch
{
//Do nothing
}
//Reload session details and return them
return new JsonNetResult()
{
Data = searchID
};
}
// GET: Search/GetResults/id
public FileStreamResult GetResults(int id)
{
//Load results from database
byte[] data = LotFinderDB.GetSearchResults(id);
//Convert results to in-memory stream
MemoryStream memoryStream = new MemoryStream(data);
//Return data
return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
{
FileDownloadName = "search_results.xlsx"
};
}
}
}
+13
View File
@@ -0,0 +1,13 @@
using System.Web.Mvc;
namespace WebInterface.Controllers
{
public class SessionsController : Controller
{
// GET: Sessions
public ActionResult Index()
{
return RedirectToActionPermanent("Index", "Search");
}
}
}