Files
jdescopingtool/openspec/changes/archive/2026-01-01-implement-domain-models/design.md
T
Joseph Doherty 26ff8d9b4f 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.
2026-01-02 07:43:29 -05:00

278 lines
10 KiB
Markdown

# Domain Models Design
## Overview
This document describes the design approach for implementing domain model entities in the JDE Scoping Tool .NET 10 migration.
## Project Organization
All domain models reside in the `JdeScoping.Core` project:
```
JdeScoping.Core/
├── Models/
│ ├── Enums/
│ │ ├── SearchStatus.cs # Search processing states
│ │ └── UpdateTypes.cs # Data sync frequency types
│ │
│ ├── Search.cs # User search request entity
│ ├── SearchCriteria.cs # Filter parameters for queries
│ ├── SearchUpdate.cs # SignalR status update DTO
│ │
│ ├── WorkOrder.cs # JDE work order entity
│ ├── WorkOrderStep.cs # Work order operation step
│ ├── WorkOrderTime.cs # F31122 time transaction
│ ├── WorkOrderComponent.cs # Component usage
│ ├── WorkOrderRouting.cs # Step transaction
│ │
│ ├── Lot.cs # JDE lot entity
│ ├── LotUsage.cs # Cardex consumption record
│ ├── LotLocation.cs # Lot location tracking
│ │
│ ├── Item.cs # JDE item master
│ ├── WorkCenter.cs # JDE work center (IBusinessUnit)
│ ├── ProfitCenter.cs # JDE profit center (IBusinessUnit)
│ ├── Branch.cs # JDE branch entity
│ ├── JdeUser.cs # JDE operator entity
│ ├── StatusCode.cs # Work order status lookup
│ ├── FunctionCode.cs # Function code lookup
│ │
│ ├── DataUpdate.cs # Cache refresh tracking
│ ├── OrgHierarchy.cs # Profit center to work center mapping
│ ├── RouteMaster.cs # Item router master
│ ├── StatusUpdate.cs # Generic process status message
│ │
│ ├── MisData.cs # CMS MIS data entity
│ │
│ ├── UserInfo.cs # Authenticated user info
│ │
│ ├── POReceiver.cs # PO receiver record
│ ├── POInspect.cs # PO inspection record
│ ├── DcsLot.cs # DCS lot record
│ ├── CamstarMO.cs # Camstar manufacturing order
│ │
│ ├── QueryTypes.cs # Query type definitions
│ ├── TableSpec.cs # Dynamic SQL table spec
│ └── ColumnSpec.cs # Column specification
├── Interfaces/
│ └── IBusinessUnit.cs # WorkCenter/ProfitCenter interface
├── ViewModels/
│ ├── WorkOrderViewModel.cs # WorkOrder projection
│ ├── LotViewModel.cs # Lot projection
│ ├── ItemViewModel.cs # Item projection
│ ├── WorkCenterViewModel.cs # WorkCenter projection
│ ├── ProfitCenterViewModel.cs # ProfitCenter projection
│ ├── JdeUserViewModel.cs # JdeUser projection
│ └── PartOperationViewModel.cs # Item/operation/MIS combination
├── Extensions/
│ ├── WorkOrderExtensions.cs # WorkOrder.ToViewModel()
│ ├── LotExtensions.cs # Lot.ToViewModel()
│ ├── ItemExtensions.cs # Item.ToViewModel()
│ ├── WorkCenterExtensions.cs # WorkCenter.ToViewModel()
│ ├── ProfitCenterExtensions.cs # ProfitCenter.ToViewModel()
│ └── JdeUserExtensions.cs # JdeUser.ToViewModel()
└── Helpers/
└── JdeDateConverter.cs # JDE date/time conversion
```
## Design Patterns
### Nullable Reference Types
The project has `<Nullable>enable</Nullable>` in the .csproj. All properties follow these patterns:
| Pattern | Example | Usage |
|---------|---------|-------|
| Required property | `public string Name { get; set; } = string.Empty;` | Non-null, initialized |
| Optional property | `public string? Description { get; set; }` | Explicitly nullable |
| Collection property | `public List<string> Items { get; set; } = [];` | Never null, empty default |
| Nullable byte array | `public byte[]? Results { get; set; }` | Null until populated |
### System.Text.Json Serialization
All enums that may be serialized use the string converter:
```csharp
using System.Text.Json.Serialization;
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum SearchStatus
{
New = 0,
Submitted = 1,
Started = 2,
Ended = 3,
Error = 4
}
```
This ensures JSON output is `"Status": "Ended"` instead of `"Status": 3`.
### JDE Date Conversion Pattern
JDE stores dates as integers in CYYDDD format (century + year + day of year) and times as HHMMSS:
```csharp
public static class JdeDateConverter
{
/// <summary>
/// Converts JDE date (CYYDDD) and time (HHMMSS) to DateTime.
/// Returns null for zero or invalid values (changed from legacy 1900-01-01).
/// </summary>
public static DateTime? ToDateTime(int jdeDate, int jdeTime = 0)
{
if (jdeDate <= 0)
return null;
try
{
// CYYDDD format: C = century (0=1900s, 1=2000s), YY = year, DDD = day of year
int century = jdeDate / 100000;
int yearInCentury = (jdeDate / 1000) % 100;
int dayOfYear = jdeDate % 1000;
int year = 1900 + (century * 100) + yearInCentury;
if (dayOfYear < 1 || dayOfYear > 366)
return null;
var date = new DateTime(year, 1, 1).AddDays(dayOfYear - 1);
// Add time component if provided
if (jdeTime > 0)
{
int hours = jdeTime / 10000;
int minutes = (jdeTime / 100) % 100;
int seconds = jdeTime % 100;
if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60 && seconds >= 0 && seconds < 60)
{
date = date.AddHours(hours).AddMinutes(minutes).AddSeconds(seconds);
}
}
return date;
}
catch
{
return null;
}
}
}
```
### Entity Computed Properties
Entities with JDE date fields use private backing fields and computed public properties:
```csharp
public class WorkOrder
{
// Private backing fields (mapped from database via Dapper)
private int LastUpdateDate { get; set; }
private int LastUpdateTime { get; set; }
// Public computed property
public DateTime? LastUpdateDT => JdeDateConverter.ToDateTime(LastUpdateDate, LastUpdateTime);
// Other properties...
public long WorkOrderNumber { get; set; }
public string ItemNumber { get; set; } = string.Empty;
}
```
### ToViewModel Extension Methods
Projections are implemented as extension methods for separation of concerns:
```csharp
public static class WorkOrderExtensions
{
public static WorkOrderViewModel ToViewModel(this WorkOrder workOrder)
{
return new WorkOrderViewModel
{
WorkOrderNumber = workOrder.WorkOrderNumber,
ItemNumber = workOrder.ItemNumber
};
}
}
```
### IBusinessUnit Interface
WorkCenter and ProfitCenter share a common interface:
```csharp
public interface IBusinessUnit
{
string Code { get; }
string Description { get; }
DateTime? LastUpdateDT { get; }
}
public class WorkCenter : IBusinessUnit
{
public string Code { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
private int LastUpdateDate { get; set; }
private int LastUpdateTime { get; set; }
public DateTime? LastUpdateDT => JdeDateConverter.ToDateTime(LastUpdateDate, LastUpdateTime);
}
```
## Classes vs Records
| Type | Pattern | Rationale |
|------|---------|-----------|
| Database entities | `class` | Mutable for Dapper mapping, private setters for computed fields |
| ViewModels | `record` or `class` | Immutable DTOs, but class is fine for simple projections |
| SearchUpdate | `class` with init | Constructor sets Timestamp to UtcNow |
| Enums | `enum` | Standard enumeration with JsonStringEnumConverter |
## Existing Code Reconciliation
The existing `JdeScoping.Core/Models/` contains placeholder implementations that differ from the spec:
| Existing File | Action | Notes |
|---------------|--------|-------|
| Search.cs | Replace | Different property names, missing CriteriaJSON/Criteria pattern |
| WorkOrder.cs | Replace | Simplified placeholder, missing JDE-specific fields |
| Item.cs | Replace | Missing ShortItemNumber, nullable annotations |
| Lot.cs | Replace | Placeholder implementation |
| LotUsage.cs | Replace | Placeholder implementation |
| WorkCenter.cs | Replace | Missing IBusinessUnit interface |
| JdeUser.cs | Replace | Placeholder implementation |
## Migration from Legacy
| Legacy Pattern | New Pattern | Rationale |
|----------------|-------------|-----------|
| `DataModel.Models` namespace | `JdeScoping.Core.Models` | .NET naming conventions |
| Newtonsoft.Json attributes | System.Text.Json attributes | Built-in .NET serialization |
| `[JsonConverter(typeof(StringEnumConverter))]` | `[JsonConverter(typeof(JsonStringEnumConverter))]` | System.Text.Json |
| `LDAPEntry` class | `UserInfo` class | Modern auth pattern naming |
| Invalid JDE dates -> 1900-01-01 | Invalid JDE dates -> null | Explicit null handling |
| ToViewModel() on entity | Extension method | Separation of concerns |
## File Structure Summary
After implementation, the Models folder structure:
```
JdeScoping.Core/Models/
├── Enums/ (2 files)
├── [Entity classes] (27 files)
├── Interfaces/ (1 file - moved from Models)
├── ViewModels/ (7 files)
├── Extensions/ (6 files)
└── Helpers/ (1 file)
```
Total: ~44 new/modified files in JdeScoping.Core