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
@@ -0,0 +1,21 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// View model for component lot filter.
/// </summary>
public class ComponentLotViewModel
{
public string LotNumber { get; set; } = string.Empty;
public string ItemNumber { get; set; } = string.Empty;
public override bool Equals(object? obj)
{
if (obj is ComponentLotViewModel other)
{
return LotNumber == other.LotNumber && ItemNumber == other.ItemNumber;
}
return false;
}
public override int GetHashCode() => HashCode.Combine(LotNumber, ItemNumber);
}
@@ -0,0 +1,24 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// View model for data refresh/sync status display.
/// </summary>
public class DataUpdateViewModel
{
public DateTime StartDt { get; set; }
public DateTime? EndDt { get; set; }
public int BranchRecords { get; set; }
public int ProfitCenterRecords { get; set; }
public int WorkCenterRecords { get; set; }
public int OrgHierarchyRecords { get; set; }
public int StatusCodeRecords { get; set; }
public int UserRecords { get; set; }
public int ItemRecords { get; set; }
public int LotRecords { get; set; }
public int WorkOrderRecords { get; set; }
public int WorkOrderStepRecords { get; set; }
public int WorkOrderComponentRecords { get; set; }
public bool WasSuccessful { get; set; }
}
@@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
namespace JdeScoping.Client.Models;
/// <summary>
/// Login form model with validation.
/// </summary>
public class LoginModel
{
[Required(ErrorMessage = "Username is required")]
public string Username { get; set; } = string.Empty;
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; } = string.Empty;
}
@@ -0,0 +1,22 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// View model for operator filter.
/// </summary>
public class OperatorViewModel
{
public int AddressNumber { get; set; }
public string UserId { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public override bool Equals(object? obj)
{
if (obj is OperatorViewModel other)
{
return UserId == other.UserId;
}
return false;
}
public override int GetHashCode() => UserId.GetHashCode();
}
@@ -0,0 +1,22 @@
using JdeScoping.Core.ViewModels;
namespace JdeScoping.Client.Models;
/// <summary>
/// View model for search criteria filters.
/// </summary>
public class SearchCriteriaViewModel
{
public DateTime? MinimumDt { get; set; }
public DateTime? MaximumDt { get; set; }
public List<WorkOrderViewModel> WorkOrders { get; set; } = [];
public List<ItemViewModel> Items { get; set; } = [];
public List<ProfitCenterViewModel> ProfitCenters { get; set; } = [];
public List<WorkCenterViewModel> WorkCenters { get; set; } = [];
public List<ComponentLotViewModel> ComponentLots { get; set; } = [];
public List<OperatorViewModel> Operators { get; set; } = [];
public List<PartOperationViewModel> PartOperations { get; set; } = [];
public bool ExtractMisData { get; set; }
}
@@ -0,0 +1,15 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// SignalR message for search status updates.
/// </summary>
public record SearchUpdate
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public string UserName { get; init; } = string.Empty;
public string Status { get; init; } = string.Empty;
public DateTime? SubmitDt { get; init; }
public DateTime? StartDt { get; init; }
public DateTime? EndDt { get; init; }
}
@@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;
namespace JdeScoping.Client.Models;
/// <summary>
/// View model for displaying search information in lists and details.
/// </summary>
public class SearchViewModel
{
public int Id { get; set; }
[Required(ErrorMessage = "Name is required.")]
public string Name { get; set; } = string.Empty;
public string UserName { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public DateTime? SubmitDt { get; set; }
public DateTime? StartDt { get; set; }
public DateTime? EndDt { get; set; }
public SearchCriteriaViewModel Criteria { get; set; } = new();
/// <summary>
/// Gets the background color based on status.
/// </summary>
public string StatusColor => Status switch
{
"Error" => "#FF6347",
"Ended" => "#90EE90",
"Running" => "#87CEEB",
_ => "#EEEEEE"
};
/// <summary>
/// Returns true if the search has downloadable results.
/// </summary>
public bool HasResults => Status == "Ended";
/// <summary>
/// Returns true if the search is read-only (already submitted).
/// </summary>
public bool IsReadOnly => Status != "New";
}
@@ -0,0 +1,10 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// SignalR message for processor status updates.
/// </summary>
public record StatusUpdate
{
public string Message { get; init; } = string.Empty;
public DateTime? Timestamp { get; init; }
}
@@ -0,0 +1,38 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// Client-side view model for authenticated user information.
/// Mirrors the server-side UserInfo model returned by /api/auth/login and /api/auth/me.
/// </summary>
public class UserInfoViewModel
{
/// <summary>
/// User's login username.
/// </summary>
public string Username { get; set; } = string.Empty;
/// <summary>
/// User's first name.
/// </summary>
public string FirstName { get; set; } = string.Empty;
/// <summary>
/// User's last name.
/// </summary>
public string LastName { get; set; } = string.Empty;
/// <summary>
/// User's display name (computed on server, provided here for convenience).
/// </summary>
public string DisplayName { get; set; } = string.Empty;
/// <summary>
/// User's organization title.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// User's email address.
/// </summary>
public string EmailAddress { get; set; } = string.Empty;
}
@@ -0,0 +1,276 @@
namespace JdeScoping.Client.Models;
/// <summary>
/// Represents a valid combination of search filter criteria.
/// Each combination defines which filter panels should be visible for a given search type.
/// </summary>
public class ValidCombination
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public bool Timespan { get; init; }
public bool WorkOrder { get; init; }
public bool ItemNumber { get; init; }
public bool ProfitCenter { get; init; }
public bool WorkCenter { get; init; }
public bool ComponentLot { get; init; }
public bool Operator { get; init; }
public bool ItemOperationMis { get; init; }
public bool ExtractMis { get; init; }
/// <summary>
/// Checks if the given filter flags match this combination.
/// </summary>
public bool Matches(
bool timespan,
bool workOrder,
bool itemNumber,
bool profitCenter,
bool workCenter,
bool componentLot,
bool @operator,
bool itemOperationMis,
bool extractMis)
{
return Timespan == timespan &&
WorkOrder == workOrder &&
ItemNumber == itemNumber &&
ProfitCenter == profitCenter &&
WorkCenter == workCenter &&
ComponentLot == componentLot &&
Operator == @operator &&
ItemOperationMis == itemOperationMis &&
ExtractMis == extractMis;
}
/// <summary>
/// Gets all 16 valid search type combinations.
/// </summary>
public static IReadOnlyList<ValidCombination> GetAll() =>
[
new ValidCombination
{
Id = 10,
Name = "Work Order",
Timespan = false,
WorkOrder = true,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 20,
Name = "Component Lot",
Timespan = false,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = false,
ComponentLot = true,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 30,
Name = "Time Span + Profit Center",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = true,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 40,
Name = "Time Span + Work Center",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = true,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 50,
Name = "Time Span + Operator",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = false,
ComponentLot = false,
Operator = true,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 60,
Name = "Time Span + Profit Center + Item Number",
Timespan = true,
WorkOrder = false,
ItemNumber = true,
ProfitCenter = true,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 70,
Name = "Time Span + Profit Center + Item/Operation/MIS",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = true,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = true,
ExtractMis = false
},
new ValidCombination
{
Id = 80,
Name = "Time Span + Profit Center + Work Order + Item/Operation/MIS",
Timespan = true,
WorkOrder = true,
ItemNumber = false,
ProfitCenter = true,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = true,
ExtractMis = false
},
new ValidCombination
{
Id = 90,
Name = "Time Span + Profit Center + Extract MIS",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = true,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = true
},
new ValidCombination
{
Id = 100,
Name = "Time Span + Work Center + Item Number",
Timespan = true,
WorkOrder = false,
ItemNumber = true,
ProfitCenter = false,
WorkCenter = true,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 110,
Name = "Time Span + Work Center + Extract MIS",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = true,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = true
},
new ValidCombination
{
Id = 120,
Name = "Time Span + Work Center + Item/Operation/MIS",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = true,
ComponentLot = false,
Operator = false,
ItemOperationMis = true,
ExtractMis = false
},
new ValidCombination
{
Id = 130,
Name = "Time Span + Work Center + Work Order + Item/Operation/MIS",
Timespan = true,
WorkOrder = true,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = true,
ComponentLot = false,
Operator = false,
ItemOperationMis = true,
ExtractMis = false
},
new ValidCombination
{
Id = 140,
Name = "Time Span + Item Number",
Timespan = true,
WorkOrder = false,
ItemNumber = true,
ProfitCenter = false,
WorkCenter = false,
ComponentLot = false,
Operator = false,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 150,
Name = "Time Span + Work Center + Operator",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = false,
WorkCenter = true,
ComponentLot = false,
Operator = true,
ItemOperationMis = false,
ExtractMis = false
},
new ValidCombination
{
Id = 160,
Name = "Time Span + Profit Center + Operator",
Timespan = true,
WorkOrder = false,
ItemNumber = false,
ProfitCenter = true,
WorkCenter = false,
ComponentLot = false,
Operator = true,
ItemOperationMis = false,
ExtractMis = false
}
];
}