604bfe919c
Apply comprehensive fixes from code reviews including: - Extract shared utilities (SqlFormatHelper, CellValueConverter, DbDestinationBase) - Add interface abstractions (IAuthenticationService, IDatabaseMigrator, IMisQueryBuilder) - Implement SecureStore for encrypted secrets storage - Fix error handling with proper HTTP status codes and logging - Optimize double enumeration in DevEtlRegistry - Add DataSync.Dev README for developer onboarding - Extract filter panel base classes to reduce duplication - Update code review docs to mark all issues as fixed
118 lines
4.0 KiB
Markdown
118 lines
4.0 KiB
Markdown
# 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 ...
|
|
|
|
/// <summary>
|
|
/// JDE date of last update to record (private backing field for Dapper mapping)
|
|
/// </summary>
|
|
private int LastUpdateDate { get; set; }
|
|
|
|
/// <summary>
|
|
/// JDE time of day of last update to record (private backing field for Dapper mapping)
|
|
/// </summary>
|
|
private int LastUpdateTime { get; set; }
|
|
|
|
/// <summary>
|
|
/// Timestamp of last update to record (computed from JDE date/time)
|
|
/// </summary>
|
|
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.
|