Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
10 KiB
Domain Models Specification - Change Delta
This document captures modifications to the base domain-models specification for the implement-domain-models change.
Base Specification
Reference: openspec/specs/domain-models/spec.md
ADDED Requirements
Requirement: JdeDateConverter helper class
The system SHALL provide a static helper class for converting JDE date/time formats to .NET DateTime.
Methods
| Method | Signature | Description |
|---|---|---|
| ToDateTime | DateTime? ToDateTime(int jdeDate, int jdeTime = 0) |
Converts JDE CYYDDD date and HHMMSS time to DateTime |
Business Rules
- Returns
nullfor zero or invalid date values (not1900-01-01) - CYYDDD format: C = century (0=1900s, 1=2000s), YY = year, DDD = day of year
- HHMMSS format: HH = hours (0-23), MM = minutes (0-59), SS = seconds (0-59)
- Invalid time values are ignored (date portion still returned)
- Parse errors return
nullrather than throwing exceptions
Scenario: Convert valid JDE date
- WHEN JdeDateConverter.ToDateTime(124365, 143052) is called
- THEN returns DateTime 2024-12-30 14:30:52
Scenario: Handle zero date
- WHEN JdeDateConverter.ToDateTime(0, 0) is called
- THEN returns null
Scenario: Handle invalid day of year
- WHEN JdeDateConverter.ToDateTime(124400, 0) is called (day 400 is invalid)
- THEN returns null
Requirement: Extension method file organization
The system SHALL organize ToViewModel extension methods in separate files by entity type.
File Structure
| File | Extension Methods |
|---|---|
| WorkOrderExtensions.cs | WorkOrder.ToViewModel() |
| LotExtensions.cs | Lot.ToViewModel() |
| ItemExtensions.cs | Item.ToViewModel() |
| WorkCenterExtensions.cs | WorkCenter.ToViewModel() |
| ProfitCenterExtensions.cs | ProfitCenter.ToViewModel() |
| JdeUserExtensions.cs | JdeUser.ToViewModel() |
Business Rules
- Extension methods in
JdeScoping.Core.Extensionsnamespace - Each file contains a single static class with extension methods for one entity type
- Extension methods are the only way entities project to ViewModels (no methods on entities)
Scenario: Use extension method for projection
- WHEN a WorkOrder entity calls ToViewModel() extension method
- THEN a WorkOrderViewModel is returned with WorkOrderNumber and ItemNumber
Requirement: ViewModel file organization
The system SHALL organize ViewModel classes in a dedicated ViewModels folder.
File Structure
| File | ViewModel Class |
|---|---|
| WorkOrderViewModel.cs | WorkOrderViewModel record |
| LotViewModel.cs | LotViewModel record |
| ItemViewModel.cs | ItemViewModel record |
| WorkCenterViewModel.cs | WorkCenterViewModel record |
| ProfitCenterViewModel.cs | ProfitCenterViewModel record |
| JdeUserViewModel.cs | JdeUserViewModel record |
| PartOperationViewModel.cs | PartOperationViewModel record |
Business Rules
- ViewModels in
JdeScoping.Core.ViewModelsnamespace - ViewModels are immutable DTOs (prefer
recordtype) - ViewModels contain only serializable properties (no computed properties)
Scenario: ViewModel serialization
- WHEN a WorkOrderViewModel is serialized to JSON
- THEN all properties are included in the output
Requirement: Enum file organization
The system SHALL organize enum types in a dedicated Enums folder.
File Structure
| File | Enum Type |
|---|---|
| SearchStatus.cs | SearchStatus enum |
| UpdateTypes.cs | UpdateTypes enum |
Business Rules
- Enums in
JdeScoping.Core.Models.Enumsnamespace - All enums that may be serialized MUST have
[JsonConverter(typeof(JsonStringEnumConverter))]
Scenario: Enum JSON serialization
- WHEN SearchStatus.Ended is serialized to JSON
- THEN the output is "Ended" (string), not 3 (integer)
MODIFIED Requirements
Requirement: Search entity
The system SHALL store user search requests containing filter criteria and resulting Excel output, with lazy deserialization of criteria from JSON.
Properties
| Property | Type | Description |
|---|---|---|
| ID | int | Primary key identifier |
| UserName | string | Username of user who created search |
| Name | string | User-friendly name for the search |
| Status | SearchStatus | Current search status (enum) |
| SubmitDT | DateTime? | Timestamp when search was submitted |
| StartDT | DateTime? | Timestamp when search processing started |
| EndDT | DateTime? | Timestamp when search completed |
| CriteriaJSON | string | JSON-serialized search criteria |
| Criteria | SearchCriteria | Deserialized search criteria object |
| Results | byte[]? | Excel file output (VARBINARY), nullable when not yet generated |
Business Rules
- Status MUST be serialized as string using
[JsonConverter(typeof(JsonStringEnumConverter))] - Criteria is stored as JSON in
CriteriaJSONfor database persistence Criteriaproperty getter deserializes fromCriteriaJSONusing System.Text.Json- Setter serializes to
CriteriaJSON - If
CriteriaJSONis null or empty,Criteriareturns a new emptySearchCriteria - Deserialization errors return empty
SearchCriteria(fail gracefully) - Results contains binary Excel file data only when Status = Ended
- Results property MUST be annotated as
byte[]?since it is null until processing completes
Scenario: Lazy deserialization of Criteria
- WHEN Search.CriteriaJSON = '{"MinimumDT":"2024-01-01"}' and Criteria is accessed
- THEN Criteria.MinimumDT = 2024-01-01
Scenario: Handle empty CriteriaJSON
- WHEN Search.CriteriaJSON = null and Criteria is accessed
- THEN Criteria returns new SearchCriteria() with all empty lists
Requirement: SearchUpdate entity
The system SHALL provide a real-time status update message for ASP.NET Core SignalR broadcast with factory method construction.
Properties
| Property | Type | Description |
|---|---|---|
| ID | int | Search primary key |
| UserName | string | Username of search submitter |
| Name | string | Search name |
| Status | SearchStatus | Current status |
| SubmitDT | DateTime? | Submit timestamp |
| StartDT | DateTime? | Start timestamp |
| EndDT | DateTime? | End timestamp |
| Timestamp | DateTime | When update was generated |
| HasResults | bool | Indicates if search has Results |
Business Rules
- Primary constructor:
SearchUpdate(Search search)copies all fields and sets Timestamp - Timestamp MUST be set to
DateTime.UtcNowwhen update is created - Status MUST be serialized as string for JSON via
[JsonConverter(typeof(JsonStringEnumConverter))] HasResultsis computed:Status == SearchStatus.Ended && search.Results != null
Scenario: Create SearchUpdate from Search
- WHEN SearchUpdate is created from a Search with ID=1, Status=Ended
- THEN SearchUpdate.ID = 1, SearchUpdate.Status = Ended, SearchUpdate.Timestamp = current UTC time
Scenario: HasResults computation
- WHEN SearchUpdate is created from Search with Status=Ended and Results is not null
- THEN HasResults = true
Requirement: UserInfo entity
The system SHALL provide authenticated user information with computed display name for ASP.NET Core Identity integration.
Properties
| Property | Type | Description |
|---|---|---|
| Username | string | User's login identifier |
| FirstName | string? | User's first name (nullable) |
| LastName | string? | User's last name (nullable) |
| DisplayName | string | Computed display name |
| Title | string? | Organization title (nullable) |
| EmailAddress | string? | Email address (nullable) |
Business Rules
- DisplayName computation:
- If FirstName and LastName both have values:
$"{FirstName} {LastName}".Trim() - If only FirstName has value:
FirstName.Trim() - If only LastName has value:
LastName.Trim() - Otherwise:
Username
- If FirstName and LastName both have values:
- "Has value" means not null and not whitespace-only
- Used for authentication context, populated from ASP.NET Core Identity claims or LDAP provider
- DN (Distinguished Name) property removed; use ClaimsPrincipal for identity information
Scenario: Compute display name from both names
- WHEN UserInfo has FirstName = "John", LastName = "Doe" and DisplayName is accessed
- THEN DisplayName = "John Doe"
Scenario: Compute display name from first name only
- WHEN UserInfo has FirstName = "John", LastName = null and DisplayName is accessed
- THEN DisplayName = "John"
Scenario: Fallback to username when names empty
- WHEN UserInfo has FirstName = null, LastName = null, Username = "jdoe" and DisplayName is accessed
- THEN DisplayName = "jdoe"
CLARIFICATIONS
Private JDE Date Fields
Entities with JDE date fields use this pattern:
public class SomeEntity
{
// These are mapped by Dapper from database columns
// but not exposed publicly
private int LastUpdateDate { get; set; }
private int LastUpdateTime { get; set; }
// Public computed property
public DateTime? LastUpdateDT => JdeDateConverter.ToDateTime(LastUpdateDate, LastUpdateTime);
}
The private setters allow Dapper to populate the values during query mapping, while the computed property provides the converted DateTime.
Collection Initialization
All collection properties use C# 12 collection expression syntax:
public List<string> Items { get; set; } = [];
This ensures collections are never null and always initialized to empty.
Namespace Organization
| Folder | Namespace |
|---|---|
| Models/ | JdeScoping.Core.Models |
| Models/Enums/ | JdeScoping.Core.Models.Enums |
| ViewModels/ | JdeScoping.Core.ViewModels |
| Extensions/ | JdeScoping.Core.Extensions |
| Interfaces/ | JdeScoping.Core.Interfaces |
| Helpers/ | JdeScoping.Core.Helpers |