Files
jdescopingtool/openspec/specs/domain-models/spec.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

1150 lines
41 KiB
Markdown

# Domain Models Specification
## Purpose
The domain model layer defines the core business entities used throughout the JDE Scoping Tool application, targeting **.NET 10** with modern C# patterns. These models represent manufacturing/ERP data from JD Edwards (JDE) and CMS (Sybase) enterprise systems, cached locally in SQL Server for efficient searching. The models serve three primary purposes:
1. **Entity representation** - Map database tables to strongly-typed C# classes with nullable reference type annotations
2. **Search criteria** - Define filter parameters for complex manufacturing queries
3. **Data transfer** - Support serialization for API communication via System.Text.Json and SignalR
## Source Reference
| Legacy Files | Purpose |
|--------------|---------|
| OLD/DataModel/Models/Search.cs | User search request with criteria and results |
| OLD/DataModel/Models/SearchCriteria.cs | JDE data filter criteria definition |
| OLD/DataModel/Models/SearchStatus.cs | Search status enumeration |
| OLD/DataModel/Models/SearchUpdate.cs | SignalR status update message |
| OLD/DataModel/Models/WorkOrder.cs | JDE work order entity |
| OLD/DataModel/Models/Lot.cs | JDE lot entity |
| OLD/DataModel/Models/LotUsage.cs | Cardex entry (lot consumption record) |
| OLD/DataModel/Models/LotLocation.cs | JDE lot location entity |
| OLD/DataModel/Models/Item.cs | JDE item (part type) entity |
| OLD/DataModel/Models/WorkCenter.cs | JDE work center entity |
| OLD/DataModel/Models/ProfitCenter.cs | JDE profit center entity |
| OLD/DataModel/Models/JdeUser.cs | JDE user (operator) entity |
| OLD/DataModel/Models/Branch.cs | JDE branch entity |
| OLD/DataModel/Models/WorkOrderStep.cs | JDE work order step entity |
| OLD/DataModel/Models/WorkOrderTime.cs | F31122 work order time transaction |
| OLD/DataModel/Models/WorkOrderRouting.cs | Work order step transaction |
| OLD/DataModel/Models/WorkOrderComponent.cs | Work order component usage |
| OLD/DataModel/Models/DataUpdate.cs | Cache data update tracking |
| OLD/DataModel/Models/StatusUpdate.cs | Process status update message |
| OLD/DataModel/Models/StatusCode.cs | JDE work order status code |
| OLD/DataModel/Models/FunctionCode.cs | JDE function code |
| OLD/DataModel/Models/OrgHierarchy.cs | Profit center to work center mapping |
| OLD/DataModel/Models/RouteMaster.cs | JDE item router master |
| OLD/DataModel/Models/MisData.cs | CMS MIS data entity |
| OLD/DataModel/Models/POReceiver.cs | JDE PO receiver record |
| OLD/DataModel/Models/POInspect.cs | JDE PO inspect record |
| OLD/DataModel/Models/DcsLot.cs | DCS lot record |
| OLD/DataModel/Models/CamstarMO.cs | Camstar manufacturing order |
| OLD/DataModel/Models/LDAPEntry.cs | LDAP search result (renamed to UserInfo) |
| OLD/DataModel/Models/IBusinessUnit.cs | Business unit interface |
| OLD/DataModel/Models/QueryTypes.cs | Query type definitions |
| OLD/DataModel/Models/TableSpec.cs | Database table specification |
| OLD/DataModel/Models/ColumnSpec.cs | Database column specification |
---
## 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 `CriteriaJSON` for database persistence
- `Criteria` property getter deserializes from `CriteriaJSON` using System.Text.Json
- Setter serializes to `CriteriaJSON`
- If `CriteriaJSON` is null or empty, `Criteria` returns a new empty `SearchCriteria`
- 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: SearchCriteria entity
The system SHALL provide filter parameters for querying JDE/CMS data.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| MinimumDT | DateTime? | Minimum timestamp to include |
| MaximumDT | DateTime? | Maximum timestamp to include |
| WorkOrderNumbers | List\<long\> | Work order numbers to filter |
| ItemNumbers | List\<string\> | Item numbers to filter |
| ProfitCenters | List\<string\> | Profit center codes to filter |
| WorkCenters | List\<string\> | Work center codes to filter |
| OperatorIDs | List\<string\> | Operator IDs to filter |
| ComponentLotNumbers | List\<LotViewModel\> | Component lot numbers to filter |
| ExtractMisData | bool | Whether to extract MIS data |
| PartOperations | List\<PartOperationViewModel\> | Part/operation combinations for MIS filtering |
#### Business Rules
- All list properties MUST be initialized to empty lists in constructor
- **Note**: Legacy has no validation - filter criteria can all be empty (validation is application-layer decision)
- ComponentLotNumbers references `LotViewModel` (lot number + item number pair)
- PartOperations references `PartOperationViewModel` (item number + operation number + MIS number + revision)
#### Scenario: Filter by work order numbers
- **WHEN** search criteria has WorkOrderNumbers = [123456, 789012] and search executes
- **THEN** only records matching those work order numbers are returned
#### Scenario: Filter by date range
- **WHEN** search criteria has MinimumDT = 2024-01-01 and MaximumDT = 2024-12-31 and search executes
- **THEN** only records within that date range are returned
---
### Requirement: SearchStatus enumeration
The system SHALL provide an enumeration of search processing states.
#### Values
| Value | Code | Description |
|-------|------|-------------|
| New | 0 | Search created but not submitted |
| Submitted | 1 | Search queued for processing |
| Started | 2 | Search processing in progress |
| Ended | 3 | Search completed successfully |
| Error | 4 | Search failed with error |
#### Business Rules
- Status transitions follow: New -> Submitted -> Started -> (Ended | Error)
- MUST be serialized as string in JSON (not integer) using `[JsonConverter(typeof(JsonStringEnumConverter))]`
#### Example: System.Text.Json enum serialization
```csharp
using System.Text.Json.Serialization;
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum SearchStatus
{
New = 0,
Submitted = 1,
Started = 2,
Ended = 3,
Error = 4
}
```
#### Scenario: Valid status transition
- **WHEN** a search has Status = Submitted and processing begins
- **THEN** Status can transition to Started
#### Scenario: Serialize status as string
- **WHEN** a search has Status = Ended and is serialized to JSON
- **THEN** status appears as "Ended" (string), not 3 (integer)
---
### 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.UtcNow` when update is created
- Status MUST be serialized as string for JSON via `[JsonConverter(typeof(JsonStringEnumConverter))]`
- `HasResults` is 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: WorkOrder entity
The system SHALL provide a JDE work order entity representing a manufacturing order.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| WorkOrderNumber | long | Unique work order identifier |
| BranchCode | string | Branch/plant code |
| LotNumber | string? | Assigned lot number (nullable) |
| ItemNumber | string | Product item number |
| ShortItemNumber | long | Numeric item identifier |
| ParentWorkOrderNumber | string? | Parent work order (if sub-assembly, nullable) |
| OrderQuantity | decimal | Quantity ordered |
| HeldQuantity | decimal | Quantity on hold |
| ShippedQuantity | decimal | Quantity shipped |
| StatusCode | string | Work order status |
| StatusCodeUpdateDT | DateTime? | Last status update timestamp |
| IssueDate | DateTime | Date work order was issued |
| StartDate | DateTime | Date work order was started |
| RoutingType | string | Routing type code |
| LastUpdateDT | DateTime | Computed from JDE date/time fields |
#### Relationships
- References `Item` via ItemNumber/ShortItemNumber
- References `Branch` via BranchCode
- References `StatusCode` via StatusCode
- Parent-child relationship via ParentWorkOrderNumber
- Has many `WorkOrderStep` records
- Has many `WorkOrderTime` records
- Has many `WorkOrderComponent` records
- Has many `LotUsage` records
#### Business Rules
- LastUpdateDT is computed property using JDE date conversion helpers
- LastUpdateDate and LastUpdateTime are private (JDE-specific format)
- ToViewModel() projects to WorkOrderViewModel (WorkOrderNumber, ItemNumber)
- LotNumber and ParentWorkOrderNumber SHOULD be annotated as nullable (`string?`)
#### Scenario: Create work order from JDE data
- **WHEN** JDE data has LastUpdateDate = 124365 and LastUpdateTime = 143052 and is mapped to WorkOrder entity
- **THEN** LastUpdateDT = 2024-12-30 14:30:52
#### Scenario: Navigate to parent work order
- **WHEN** work order 12345 has ParentWorkOrderNumber = "11111" and the parent-child relationship is traversed
- **THEN** the parent work order 11111 is accessible
#### Scenario: Project to ViewModel
- **WHEN** a WorkOrder with WorkOrderNumber = 12345, ItemNumber = "ABC123" calls ToViewModel()
- **THEN** WorkOrderViewModel contains only WorkOrderNumber and ItemNumber
---
### Requirement: Lot entity
The system SHALL provide a JDE lot entity representing a tracked batch of materials.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| LotNumber | string | Unique lot identifier |
| BranchCode | string | Business unit code |
| ShortItemNumber | long | Numeric item identifier |
| ItemNumber | string | Item number |
| SupplierCode | long | Supplier address number |
| StatusCode | char | Single-character lot status |
| Memo1 | string? | Memo line 1 (nullable) |
| Memo2 | string? | Memo line 2 (nullable) |
| Memo3 | string? | Memo line 3 (nullable) |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Relationships
- References `Item` via ItemNumber/ShortItemNumber
- References `Branch` via BranchCode
- Has many `LotLocation` records
- Has many `LotUsage` records
#### Business Rules
- StatusCode is single character (not string)
- ToViewModel() projects to LotViewModel (LotNumber, ItemNumber)
- Memo fields SHOULD be annotated as nullable (`string?`)
#### Scenario: Lot with single-character status
- **WHEN** a lot has StatusCode = 'A' (active) and the lot entity is read
- **THEN** StatusCode is char type, not string
#### Scenario: Project lot to ViewModel
- **WHEN** a Lot with LotNumber = "LOT001", ItemNumber = "ITEM123" calls ToViewModel()
- **THEN** LotViewModel contains LotNumber and ItemNumber only
---
### Requirement: LotUsage entity
The system SHALL provide a cardex entry recording component consumption in work orders.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| UniqueID | long | Primary key identifier |
| WorkOrderNumber | long | Associated work order |
| LotNumber | string | Component lot number |
| BranchCode | string | Branch code |
| ShortItemNumber | long | Component item number |
| Quantity | decimal | Transaction quantity |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Relationships
- References `WorkOrder` via WorkOrderNumber
- References `Lot` via LotNumber
- References `Item` via ShortItemNumber
---
### Requirement: LotLocation entity
The system SHALL provide JDE lot location tracking.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| LotNumber | string | Lot identifier |
| ShortItemNumber | long | Item identifier |
| BranchCode | string | Business unit code |
| Location | string | Physical location code |
| LastUpdateDT | DateTime | Last update timestamp |
#### Relationships
- References `Lot` via LotNumber
- References `Item` via ShortItemNumber
- References `Branch` via BranchCode
---
### Requirement: Item entity
The system SHALL provide a JDE item (part type) master entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| ShortItemNumber | long | Unique numeric identifier |
| ItemNumber | string | Alphanumeric item number |
| Description | string | Item description |
| PlanningFamily | string? | Master planning family (nullable) |
| StockingType | string? | Stocking type code (nullable) |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Business Rules
- ShortItemNumber is the numeric key used in JDE joins
- ItemNumber is the human-readable identifier
- ToViewModel() projects to ItemViewModel (ItemNumber, Description)
#### Scenario: Dual identifier pattern
- **WHEN** an item has ShortItemNumber = 12345 and ItemNumber = "ABC-123" and joins with work orders
- **THEN** ShortItemNumber is used for database joins and ItemNumber is displayed to users
---
### Requirement: WorkCenter entity
The system SHALL provide a JDE work center entity (implements IBusinessUnit).
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Unique work center code |
| Description | string | Work center description |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Business Rules
- Implements `IBusinessUnit` interface
- ToViewModel() projects to WorkCenterViewModel (Code, Description)
---
### Requirement: ProfitCenter entity
The system SHALL provide a JDE profit center entity (implements IBusinessUnit).
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Unique profit center code |
| Description | string | Profit center description |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Business Rules
- Implements `IBusinessUnit` interface
- ToViewModel() projects to ProfitCenterViewModel (Code, Description)
---
### Requirement: JdeUser entity
The system SHALL provide a JDE user (operator) entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| AddressNumber | long | Unique address number |
| UserID | string | Login identifier |
| FullName | string | Full name (last, first [middle]) |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Business Rules
- AddressNumber is the JDE primary key
- UserID is the login identifier
- ToViewModel() projects to JdeUserViewModel (AddressNumber, UserID, FullName)
---
### Requirement: Branch entity
The system SHALL provide a JDE branch entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Unique branch code |
| Description | string | Branch description |
| LastUpdateDT | DateTime | Computed from JDE date/time |
---
### Requirement: IBusinessUnit interface
The system SHALL provide an interface for business unit entities (WorkCenter, ProfitCenter).
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Business unit unique code |
| Description | string | Business unit description |
| LastUpdateDT | DateTime | Last update timestamp |
---
### Requirement: WorkOrderStep entity
The system SHALL provide a JDE work order operation step.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| WorkOrderNumber | long | Work order identifier |
| BranchCode | string | Branch code |
| WorkCenterCode | string | Work center code |
| StepNumber | decimal | Operation sequence number |
| StepDescription | string? | Step description (nullable) |
| FunctionOperationDescription | string? | Long text description (nullable) |
| StepTypeCode | string | Operation type |
| StartDT | DateTime? | Step start timestamp |
| EndDT | DateTime? | Step end timestamp |
| FunctionCode | string | Operation function code |
| ScrappedQuantity | decimal | Quantity scrapped/cancelled |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Relationships
- References `WorkOrder` via WorkOrderNumber
- References `WorkCenter` via WorkCenterCode
- References `FunctionCode` via FunctionCode
---
### Requirement: WorkOrderTime entity
The system SHALL provide an F31122 work order time transaction record.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| UniqueID | long | Primary key |
| WorkOrderNumber | long | Work order identifier |
| BranchCode | string | Branch code |
| WorkCenterCode | string | Work center code |
| StepNumber | decimal | Operation sequence |
| AddressNumber | long | Operator address number |
| GlDate | DateTime? | G/L processing date |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Relationships
- References `WorkOrder` via WorkOrderNumber
- References `JdeUser` via AddressNumber
- References `WorkCenter` via WorkCenterCode
---
### Requirement: WorkOrderRouting entity
The system SHALL provide a work order step transaction model.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| UserID | string | Transaction user ID |
| BatchNumber | string | Transaction batch number |
| TransactionNumber | string | Transaction number |
| LineNumber | int | Transaction line number |
| StepNumber | decimal | Operation sequence |
| WorkCenterCode | string | Work center code |
| WorkOrderNumber | long | Work order identifier |
| RoutingType | string | Routing type |
| BranchCode | string | Branch code |
| StepDescription | string? | Step description (nullable) |
| FunctionCode | string | Function code |
| TransactionDate | DateTime | Transaction original date |
| LastUpdateDT | DateTime | Computed from JDE date/time |
---
### Requirement: WorkOrderComponent entity
The system SHALL provide a work order component usage model.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| UniqueID | long | Primary key |
| WorkOrderNumber | long | Work order identifier |
| LotNumber | string? | Component lot number (nullable) |
| BranchCode | string | Branch code |
| ShortItemNumber | long? | Component item (nullable) |
| Quantity | decimal | Transaction quantity |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Business Rules
- ShortItemNumber is nullable (unlike LotUsage)
- LotNumber SHOULD be annotated as nullable (`string?`)
---
### Requirement: DataUpdate entity
The system SHALL provide a cache data update tracking entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| ID | int | Primary key |
| SourceSystem | string | Source system name (JDE/CMS) |
| SourceData | string | Source data type |
| TableName | string | Cache table name |
| StartDT | DateTime | Update start timestamp |
| EndDT | DateTime | Update end timestamp |
| UpdateType | UpdateTypes | Type of update (enum) |
| WasSuccessful | bool | Success indicator |
| NumberRecords | long | Record count |
#### Business Rules
- UpdateTypes enum: Hourly (1), Daily (2), Mass (3)
- Used to track cache refresh status and timing
- UpdateType MUST use `[JsonConverter(typeof(JsonStringEnumConverter))]` if serialized
#### Scenario: Track successful daily update
- **WHEN** a daily sync of WorkOrder table completes and DataUpdate is created
- **THEN** SourceSystem = "JDE", UpdateType = Daily (2), WasSuccessful = true
#### Scenario: Track failed mass update
- **WHEN** a mass refresh fails with error and DataUpdate is created
- **THEN** WasSuccessful = false, NumberRecords = 0
---
### Requirement: UpdateTypes enumeration
The system SHALL provide an enumeration for data update frequency types.
#### Values
| Value | Code | Description |
|-------|------|-------------|
| Hourly | 1 | Hourly incremental update |
| Daily | 2 | Daily incremental update |
| Mass | 3 | Full data refresh |
#### Business Rules
- SHOULD use `[JsonConverter(typeof(JsonStringEnumConverter))]` if serialized to JSON
---
### Requirement: StatusUpdate entity
The system SHALL provide a generic process status update message (not search-specific).
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Message | string | Update message text |
| Timestamp | DateTime | Message timestamp |
---
### Requirement: StatusCode entity
The system SHALL provide a JDE work order status code lookup.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Unique status code |
| Description | string | Status description |
| LastUpdateDT | DateTime | Computed from JDE date/time |
---
### Requirement: FunctionCode entity
The system SHALL provide a JDE function code lookup.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Unique function code |
| Description | string | Function description |
| LastUpdateDT | DateTime | Last update timestamp |
---
### Requirement: OrgHierarchy entity
The system SHALL provide organization hierarchy mapping (profit center to work center).
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| WorkCenterCode | string | Work center code |
| BranchCode | string | Branch unit code |
| ProfitCenterCode | string | Profit center code |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Relationships
- Maps `WorkCenter` to `ProfitCenter`
- References `Branch` via BranchCode
---
### Requirement: RouteMaster entity
The system SHALL provide a JDE item router master entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| BranchCode | string | Branch code |
| ItemNumber | string | Item number |
| RoutingType | string | Router type |
| SequenceNumber | decimal | Job step number |
| FunctionCode | string | Function code |
| WorkCenterCode | string | Work center code |
| StartDate | DateTime | Effectivity start date |
| EndDate | DateTime? | Effectivity end date (nullable) |
| LastUpdateDT | DateTime | Computed from JDE date/time |
#### Business Rules
- StartDate_Date and EndDate_Date store JDE integer format
- StartDate and EndDate are computed properties using JDE conversion
---
### Requirement: MisData entity
The system SHALL provide a CMS MIS (Manufacturing Information System) data entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| ItemNumber | string | Item number |
| BranchCode | string | Branch code |
| SequenceNumber | string | Operation job step number |
| MisNumber | string | MIS unique number |
| RevID | string? | MIS revision ID (nullable) |
| CharNumber | string | Characteristic number |
| TestDescription | string? | Test description (nullable) |
| SamplingType | string? | Type of sampling (nullable) |
| SamplingValue | string? | Sampling selection value (nullable) |
| ToolsGauges | string? | Tools and gauges (nullable) |
| WorkInstructions | string? | Work instructions (nullable) |
| Status | string | Release status |
| ReleaseDate | DateTime? | Release date |
#### Business Rules
- Sourced from CMS (Sybase), not JDE (Oracle)
- Only extracted when SearchCriteria.ExtractMisData = true
- Many string fields SHOULD be annotated as nullable (`string?`)
#### Scenario: Extract MIS data when requested
- **WHEN** SearchCriteria has ExtractMisData = true and search executes
- **THEN** MisData records are fetched from CMS Sybase
#### Scenario: Skip MIS data when not requested
- **WHEN** SearchCriteria has ExtractMisData = false and search executes
- **THEN** MisData query is skipped (performance optimization)
---
### Requirement: POReceiver entity
The system SHALL provide a JDE purchase order receiver record.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| OrderNumber | long | PO number |
| OrderCompany | string | PO company code |
| OrderSuffix | string | PO suffix |
| LineNumber | decimal | Line number |
| NumberOfLines | int | Total lines in PO |
| InvoiceNumber | long | Invoice number |
| BranchCode | string | Receiving site |
| LotNumber | string? | Product lot number (nullable) |
| ShortItemNumber | string | Item number (string - legacy type) |
| DateReceived | DateTime | Receipt date |
| Subledger | string? | Subledger name (nullable) |
| QtyReceived | decimal | Quantity received |
| LastUpdateDT | DateTime | Last update timestamp |
---
### Requirement: POInspect entity
The system SHALL provide a JDE purchase order inspection record.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| UniqueID | long | Primary key |
| OrderNumber | long | PO number |
| OrderCompany | string | Company code |
| LineNumber | decimal | Line number |
| InvoiceNumber | long | Invoice number |
| LotNumber | string? | Lot number (nullable) |
| ShortItemNumber | string | Item number |
| LastUpdateDT | DateTime | Last update timestamp |
---
### Requirement: DcsLot entity
The system SHALL provide a DCS lot record entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| ItemNumber | string | Item number |
| LotNumber | string | Lot number |
| LotSuffix | string? | Lot suffix (nullable) |
| LastUpdateDT | DateTime | Last update timestamp |
---
### Requirement: CamstarMO entity
The system SHALL provide a Camstar manufacturing order entity.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| MONumber | string | Manufacturing order number |
| LastUpdateDT | DateTime | Last update timestamp |
---
### 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:
1. If FirstName and LastName both have values: `$"{FirstName} {LastName}".Trim()`
2. If only FirstName has value: `FirstName.Trim()`
3. If only LastName has value: `LastName.Trim()`
4. Otherwise: `Username`
- "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"
---
### Requirement: QueryTypes entity
The system SHALL provide query type definitions for search filtering.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Code | string | Query type code |
| Name | string | Query type name |
| OrderIndex | int | Display order |
| TimeSpanFilter | bool | Supports date range filter |
| WorkOrderFilter | bool | Supports work order filter |
| ItemNumberFilter | bool | Supports item filter |
| ProfitCenterFilter | bool | Supports profit center filter |
| WorkCenterFilter | bool | Supports work center filter |
| ComponentLotFilter | bool | Supports lot filter |
| OperatorFilter | bool | Supports operator filter |
| ItemOperationMISFilter | bool | Supports item/operation MIS filter |
| ExtractMISFilter | bool | Supports MIS extraction |
| ReceivedItemNumberIISFilter | bool | Supports received item filter |
#### Business Rules
- Uses static dictionary for type registration
- Identify() method maps SearchCriteria to QueryTypes (stub implementation)
- Predefined type: WorkOrder with WorkOrderFilter = true
---
### Requirement: TableSpec entity
The system SHALL provide a database table specification for dynamic SQL generation.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Name | string | Table name |
| TempTableName | string | Computed: #{Name} |
| Columns | List\<ColumnSpec\> | Table columns |
| PrimaryKey | List\<ColumnSpec\> | Primary key columns |
#### Business Rules
- Constructor initializes Columns and PrimaryKey to empty lists
- Stub methods: GenerateIndex(), GenerateDrop(), GenerateCreate(), GetColumn()
---
### Requirement: ColumnSpec entity
The system SHALL provide a database column specification.
#### Properties
| Property | Type | Description |
|----------|------|-------------|
| Name | string | Column name |
| Definition | string | Column definition |
---
### 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 `null` for zero or invalid date values (not `1900-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 `null` rather 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.Extensions` namespace
- 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.ViewModels` namespace
- ViewModels are immutable DTOs (prefer `record` type)
- 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.Enums` namespace
- 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)
---
## Nullable Reference Types
The .NET 10 project MUST enable nullable reference types in the project file:
```xml
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
```
### Nullable Annotation Guidelines
| Pattern | Example | Usage |
|---------|---------|-------|
| Required property | `string Name { get; set; }` | Non-null, must be initialized |
| Optional property | `string? Description { get; set; }` | Can be null |
| Required with default | `string Name { get; set; } = string.Empty;` | Non-null with safe default |
| Collection property | `List<string> Items { get; set; } = [];` | Never null, empty by default |
### Properties Requiring Nullable Annotation
The following properties SHOULD be annotated as nullable based on business rules:
- `Search.Results` -> `byte[]?`
- `WorkOrder.LotNumber`, `ParentWorkOrderNumber` -> `string?`
- `Lot.Memo1`, `Memo2`, `Memo3` -> `string?`
- `Item.PlanningFamily`, `StockingType` -> `string?`
- `WorkOrderStep.StepDescription`, `FunctionOperationDescription` -> `string?`
- `WorkOrderRouting.StepDescription` -> `string?`
- `WorkOrderComponent.LotNumber` -> `string?`
- `MisData` optional fields -> `string?`
- `UserInfo.FirstName`, `LastName`, `Title`, `EmailAddress` -> `string?`
---
## JDE Date/Time Conversion Pattern
Many JDE entities store dates and times in integer format that must be converted:
### JDE Date Format
- Integer format: CYYDDD (century + year + day of year)
- Century: 0 = 1900s, 1 = 2000s
- Example: 124365 = December 30, 2024 (1 + 24 years + 365th day)
### JDE Time Format
- Integer format: HHMMSS
- Example: 143052 = 14:30:52
### Edge Cases
- **Invalid/Zero dates**: Return `null` for 0 or invalid date values (changed from legacy `1900-01-01`)
- **Parse errors**: Return `null` rather than silently swallowing errors
- **Backing field access**: Raw JDE integer values stored in private fields (e.g., `TransactionDate_Date`)
### Implementation Pattern
```csharp
// Private backing fields (mapped from database)
private int LastUpdateDate { get; }
private int LastUpdateTime { get; }
// Public computed property (nullable for invalid dates)
public DateTime? LastUpdateDT => JdeDateConverter.ToDateTime(LastUpdateDate, LastUpdateTime);
```
---
## ToViewModel Extension Methods
The ToViewModel() pattern from the legacy codebase is preserved using extension methods:
```csharp
public static class WorkOrderExtensions
{
public static WorkOrderViewModel ToViewModel(this WorkOrder workOrder)
{
return new WorkOrderViewModel
{
WorkOrderNumber = workOrder.WorkOrderNumber,
ItemNumber = workOrder.ItemNumber
};
}
}
```
### Alternative Mapping Approaches
For larger mapping scenarios, consider using a mapping library:
- **AutoMapper**: Convention-based mapping with profiles
- **Mapster**: Faster alternative with code generation support
The extension method pattern is preferred for simple projections to maintain explicit, testable code.
---
## Migration Notes
| Legacy Pattern | New Pattern | Rationale |
|----------------|-------------|-----------|
| `DataModel.Models` namespace | `JdeScoping.Core.Models` | .NET naming conventions |
| Newtonsoft.Json | System.Text.Json | Built-in .NET serialization |
| `[JsonConverter(typeof(StringEnumConverter))]` | `[JsonConverter(typeof(JsonStringEnumConverter))]` | System.Text.Json enum converter |
| `LDAPEntry` | `UserInfo` | Renamed for modern auth patterns |
| `<Nullable>disable</Nullable>` | `<Nullable>enable</Nullable>` | Improved null safety |
| Forms Authentication models | ASP.NET Core cookie authentication | Modern auth patterns |
| Private setters for JDE fields | Private backing fields | Maintain encapsulation |
| Extension methods for JDE conversion | Static helper class `JdeDateConverter` | Cleaner separation |
| `List<T>` for collections | Consider `IReadOnlyList<T>` for immutability | Defensive design |
| ToViewModel() methods on entities | Extension methods in separate file | Separation of concerns |
| `IBusinessUnit` interface | Consider sealed classes with shared base | Modern C# patterns |
| JDE invalid dates return `1900-01-01` | Return `null` for invalid dates | Explicit null handling |
| SignalR (OWIN-based) | ASP.NET Core SignalR | Modern SignalR implementation |
---
## Resolved Design Decisions
The following questions from the legacy analysis have been resolved:
| Question | Decision | Rationale |
|----------|----------|-----------|
| Search.Results storage | Keep VARBINARY storage | Blob storage adds infrastructure complexity; VARBINARY is sufficient for Excel files |
| SearchCriteria validation | Keep validation at service layer | Domain models remain clean; validation is application concern |
| JDE date conversion | Use static helper class | Dapper type handlers are fragile; explicit conversion is more testable |
| Entity vs DTO separation | Use ToViewModel extension methods | Provides separation without class explosion |
| QueryTypes.Identify() | Remove stub; implement at service layer | Query type determination belongs in business logic |
| TableSpec/ColumnSpec | Keep for now | May be needed for dynamic query generation |
| MisData source | Continue CMS integration | Active business requirement |
| POReceiver.ShortItemNumber as string | Preserve as string | Intentional legacy typing; document as known quirk |
| LDAPEntry rename | Rename to `UserInfo` | Supports both LDAP and other identity providers |
| CamstarMO/DcsLot status | Keep but mark as optional | May be deprecated; implementation can skip if not needed |
| Status transition enforcement | Keep simple enum | Add state machine if needed at service layer |
| SearchUpdate.Timestamp default | Always set in constructor | Use primary constructor or init required |
| JDE invalid date handling | Return `null` | Explicit null is clearer than magic date |
| JDE backing fields | Expose only DateTime properties | Consumers don't need raw JDE integers |
---
## Codex Review Findings
The following issues were identified during code review and should be addressed:
### SearchCriteria Validation Gap
- **Issue**: No validation exists - all criteria lists can be empty
- **Impact**: Users could submit searches with no filters, causing full table scans
- **Recommendation**: Add service-layer validation requiring at least one filter criterion
### JDE Date Edge Cases
- **Issue**: Legacy silently returns `1900-01-01` for invalid dates
- **Impact**: Difficult to distinguish invalid data from legitimate old dates
- **Recommendation**: Return `null` for invalid JDE dates; update consuming code to handle nulls
### POReceiver Type Inconsistency
- **Issue**: `ShortItemNumber` is `string` in POReceiver but `long` elsewhere
- **Impact**: Potential join/comparison issues
- **Recommendation**: Document as intentional legacy behavior; add explicit conversion if needed
### QueryTypes Incomplete Implementation
- **Issue**: `Identify()` method is stubbed with no implementation
- **Impact**: Query type detection doesn't work
- **Recommendation**: Implement at service layer based on populated criteria fields