# 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\ | Work order numbers to filter | | ItemNumbers | List\ | Item numbers to filter | | ProfitCenters | List\ | Profit center codes to filter | | WorkCenters | List\ | Work center codes to filter | | OperatorIDs | List\ | Operator IDs to filter | | ComponentLotNumbers | List\ | Component lot numbers to filter | | ExtractMisData | bool | Whether to extract MIS data | | PartOperations | List\ | 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\ | Table columns | | PrimaryKey | List\ | 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 enable ``` ### 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 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 | | `disable` | `enable` | 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` for collections | Consider `IReadOnlyList` 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