# JDE Date Conversion Pattern ## Overview This document explains the JDE (JD Edwards) date conversion pattern used throughout the data model classes. This pattern appears in 15+ model files and is **intentionally repeated** rather than abstracted—it is required boilerplate for Dapper ORM column mapping. ## JDE Date Format JD Edwards stores dates in a proprietary **Julian date format** called CYYDDD: | Component | Description | Example | |-----------|-------------|---------| | C | Century indicator (0=1900s, 1=2000s) | 1 | | YY | Year within century | 25 | | DDD | Day of year (001-366) | 019 | **Example:** January 19, 2025 = `125019` - Century: 1 (2000s) - Year: 25 (2025) - Day: 019 (19th day of year) JDE also stores time separately as **HHMMSS**: - 2:30:45 PM = `143045` ## The Pattern ### Why Private Backing Fields? Dapper requires a settable property to map database columns. Since we want: 1. The raw JDE integer values mapped from the database 2. A clean `DateTime?` exposed to consumers 3. No public setters on the converted dates We use this pattern: ```csharp using JdeScoping.Core.Helpers; public class WorkOrder { // ... other properties ... /// /// JDE date of last update to record (private backing field for Dapper mapping) /// private int LastUpdateDate { get; set; } /// /// JDE time of day of last update to record (private backing field for Dapper mapping) /// private int LastUpdateTime { get; set; } /// /// Timestamp of last update to record (computed from JDE date/time) /// public DateTime? LastUpdateDt => JdeDateConverter.ToDateTime(LastUpdateDate, LastUpdateTime); } ``` ### Why This Cannot Be Abstracted Further 1. **Dapper requires properties on the model class itself** - It cannot map to properties on a composed helper object. 2. **Each model has different date fields** - WorkOrder has `LastUpdateDate/Time`, WorkOrderStep has `EndDate/Time`, etc. The field names vary per entity. 3. **Some models have date-only, some have date+time** - The pattern adapts to each entity's needs. 4. **Private backing fields must match SQL column aliases** - Dapper maps by property name matching column name. ## Centralized Helper The actual conversion logic **is** centralized in: ``` NEW/src/JdeScoping.Core/Helpers/JdeDateConverter.cs ``` This static helper provides: - `ToDateTime(int jdeDate)` - Date only conversion - `ToDateTime(int jdeDate, int jdeTime)` - Date + time conversion - `ToJdeDate(this DateTime)` - Convert DateTime to JDE date - `ToJdeTime(this DateTime)` - Convert DateTime to JDE time ## Files Using This Pattern The pattern appears in these model files: - `Models/Inventory/Item.cs` - `Models/Inventory/Lot.cs` - `Models/Inventory/LotUsage.cs` - `Models/Lookup/StatusCode.cs` - `Models/Organization/Branch.cs` - `Models/Organization/JdeUser.cs` - `Models/Organization/OrgHierarchy.cs` - `Models/Organization/ProfitCenter.cs` - `Models/Organization/RouteMaster.cs` - `Models/Organization/WorkCenter.cs` - `Models/WorkOrders/WorkOrder.cs` - `Models/WorkOrders/WorkOrderComponent.cs` - `Models/WorkOrders/WorkOrderRouting.cs` - `Models/WorkOrders/WorkOrderStep.cs` - `Models/WorkOrders/WorkOrderTime.cs` ## Why Not Records or Init-Only Properties? Using records with init-only properties would prevent Dapper from setting the values since Dapper uses property setters for mapping. The private setter approach is the cleanest pattern that: 1. Hides raw JDE integers from consumers 2. Exposes clean nullable DateTime 3. Works with Dapper's property-based mapping ## Summary **This is not code duplication** - it is the minimum required boilerplate for: - Database column mapping (private field + Dapper) - Type conversion (JdeDateConverter helper) - Clean public API (computed DateTime property) Each repetition is 3 lines of trivial code that cannot be abstracted without breaking Dapper mapping or over-engineering the solution.