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.
This commit is contained in:
Joseph Doherty
2026-01-02 07:43:29 -05:00
commit 26ff8d9b4f
1761 changed files with 596509 additions and 0 deletions
@@ -0,0 +1,294 @@
# Solution Foundation Design
## Overview
This document describes the infrastructure architecture for the .NET 10 solution, including project structure, dependency injection patterns, and configuration management.
## Project Structure
```
NEW/src/
├── JdeScoping.Core/ # Domain models, interfaces, shared logic
│ ├── Models/ # Entity classes (Search, WorkOrder, Lot, etc.)
│ ├── Interfaces/ # Service contracts (ISearchRepository, etc.)
│ ├── Options/ # Configuration binding classes
│ └── Extensions/ # Service registration extension methods
├── JdeScoping.Host/ # ASP.NET Core host (Web API + BackgroundServices)
│ ├── Program.cs # Application entry point, DI configuration
│ ├── appsettings.json # Production configuration
│ ├── appsettings.Development.json # Development overrides
│ └── Controllers/ # API endpoints
├── JdeScoping.Client/ # Blazor WebAssembly UI
│ └── (deferred to UI phase)
└── JdeScoping.Database/ # DbUp migrations (already exists)
├── Scripts/ # Migration SQL files
└── DatabaseMigrator.cs # DbUp configuration
```
## DI Registration Pattern
### Extension Method Convention
Each module provides an extension method on `IServiceCollection`:
```csharp
public static class DataAccessServiceExtensions
{
public static IServiceCollection AddDataAccess(
this IServiceCollection services,
IConfiguration configuration)
{
// Bind configuration
services.Configure<DataAccessOptions>(
configuration.GetSection(DataAccessOptions.SectionName));
// Register services
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
services.AddScoped<IJdeRepository, JdeRepository>();
services.AddScoped<ICmsRepository, CmsRepository>();
return services;
}
}
```
### Module Registration Order
Extensions are called in dependency order in Program.cs:
```csharp
builder.Services
.AddDataAccess(builder.Configuration) // 1. Database access
.AddDataSync(builder.Configuration) // 2. Cache synchronization
.AddSearchProcessing(builder.Configuration) // 3. Search execution
.AddExcelExport(builder.Configuration) // 4. Result export
.AddAuth(builder.Configuration); // 5. Authentication
```
### Lifetime Guidelines
| Service Type | Lifetime | Rationale |
|--------------|----------|-----------|
| Repository | Scoped | Database connection per request |
| DbContext (if used) | Scoped | EF Core default |
| Options classes | Singleton | Cached configuration |
| HttpClient | Singleton | Connection pooling |
| BackgroundService | Singleton | Long-running workers |
| Processors | Transient | Stateless operations |
## Configuration Sections
### appsettings.json Structure
```json
{
"ConnectionStrings": {
"LotFinder": "Server=...;Database=LotFinder;...",
"JDE": "Data Source=...;User ID=...;Password=...",
"CMS": "Data Source=...;Port=...;Database=..."
},
"DataAccess": {
"CommandTimeoutSeconds": 120,
"EnableDetailedLogging": false
},
"DataSync": {
"MassRefreshCronSchedule": "0 0 6 * * SAT",
"DailyRefreshCronSchedule": "0 0 4 * * *",
"HourlyRefreshCronSchedule": "0 0 * * * *",
"MaxConcurrentUpdates": 4
},
"Auth": {
"LdapUrl": "LDAP://directory.company.com",
"LdapGroup": "CN=LotFinderUsers,OU=Groups,DC=company,DC=com",
"CookieExpirationMinutes": 480
},
"ExcelExport": {
"TempDirectory": "/tmp/lotfinder",
"MaxRowsPerSheet": 1048576,
"DefaultDateFormat": "yyyy-MM-dd HH:mm:ss"
},
"SearchProcessing": {
"PollingIntervalSeconds": 5,
"MaxConcurrentSearches": 2,
"SearchTimeoutMinutes": 30
}
}
```
### appsettings.Development.json Overrides
```json
{
"ConnectionStrings": {
"LotFinder": "Server=localhost,1434;Database=LotFinder;User Id=scopingapp;Password=...;TrustServerCertificate=True"
},
"DataAccess": {
"EnableDetailedLogging": true
},
"DataSync": {
"MassRefreshCronSchedule": "",
"DailyRefreshCronSchedule": "",
"HourlyRefreshCronSchedule": ""
},
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
}
}
```
## Options Classes
### Naming Convention
- Class name: `{Module}Options`
- Section name: Same as class name without "Options" suffix
- Static constant: `SectionName` for configuration binding
### DataAccessOptions
```csharp
public class DataAccessOptions
{
public const string SectionName = "DataAccess";
public int CommandTimeoutSeconds { get; set; } = 120;
public bool EnableDetailedLogging { get; set; } = false;
}
```
### DataSyncOptions
```csharp
public class DataSyncOptions
{
public const string SectionName = "DataSync";
public string MassRefreshCronSchedule { get; set; } = "0 0 6 * * SAT";
public string DailyRefreshCronSchedule { get; set; } = "0 0 4 * * *";
public string HourlyRefreshCronSchedule { get; set; } = "0 0 * * * *";
public int MaxConcurrentUpdates { get; set; } = 4;
}
```
### AuthOptions
```csharp
public class AuthOptions
{
public const string SectionName = "Auth";
public string LdapUrl { get; set; } = string.Empty;
public string LdapGroup { get; set; } = string.Empty;
public int CookieExpirationMinutes { get; set; } = 480;
}
```
### ExcelExportOptions
```csharp
public class ExcelExportOptions
{
public const string SectionName = "ExcelExport";
public string TempDirectory { get; set; } = "/tmp/lotfinder";
public int MaxRowsPerSheet { get; set; } = 1048576;
public string DefaultDateFormat { get; set; } = "yyyy-MM-dd HH:mm:ss";
}
```
### SearchProcessingOptions
```csharp
public class SearchProcessingOptions
{
public const string SectionName = "SearchProcessing";
public int PollingIntervalSeconds { get; set; } = 5;
public int MaxConcurrentSearches { get; set; } = 2;
public int SearchTimeoutMinutes { get; set; } = 30;
}
```
## NuGet Package Dependencies
### JdeScoping.Core
```xml
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
```
### JdeScoping.Host
```xml
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.0" />
<PackageReference Include="Dapper" Version="2.1.35" />
<PackageReference Include="EPPlus" Version="7.0.0" />
<PackageReference Include="DbUp-SqlServer" Version="5.0.37" />
<PackageReference Include="Quartz" Version="3.8.0" />
```
## Program.cs Structure
```csharp
var builder = WebApplication.CreateBuilder(args);
// Database migrations
DatabaseMigrator.Migrate(builder.Configuration.GetConnectionString("LotFinder")!);
// Module registration
builder.Services
.AddDataAccess(builder.Configuration)
.AddDataSync(builder.Configuration)
.AddSearchProcessing(builder.Configuration)
.AddExcelExport(builder.Configuration)
.AddAuth(builder.Configuration);
// ASP.NET Core services
builder.Services.AddControllers();
builder.Services.AddSignalR();
var app = builder.Build();
// Middleware pipeline
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHub<StatusHub>("/statushub");
app.Run();
```
## Validation Approach
### Startup Validation
Validate critical services are registered at startup:
```csharp
// In Program.cs after building
using var scope = app.Services.CreateScope();
_ = scope.ServiceProvider.GetRequiredService<ILotFinderRepository>();
_ = scope.ServiceProvider.GetRequiredService<IOptions<DataAccessOptions>>();
// ... validate other critical services
```
### Configuration Validation
Use DataAnnotations or IValidateOptions for configuration:
```csharp
public class DataAccessOptionsValidator : IValidateOptions<DataAccessOptions>
{
public ValidateOptionsResult Validate(string? name, DataAccessOptions options)
{
if (options.CommandTimeoutSeconds <= 0)
return ValidateOptionsResult.Fail("CommandTimeoutSeconds must be positive");
return ValidateOptionsResult.Success;
}
}
```
@@ -0,0 +1,57 @@
# Setup Solution Foundation
## Summary
Set up the .NET 10 solution infrastructure including dependency injection configuration, options pattern for configuration binding, and modular service registration extension methods. This establishes the architectural foundation for all subsequent migration phases.
## Scope
### In Scope
- Program.cs with DI container configuration
- appsettings.json structure with sections for each module
- Service registration extension methods (AddDataAccess, AddDataSync, AddAuth, AddExcelExport)
- Options classes for IOptions<T> configuration binding
- Project references and NuGet package dependencies
### Out of Scope
- Actual service implementations (deferred to domain-specific phases)
- Database connection testing (covered by migrate-database-schema)
- Authentication implementation details (deferred to auth phase)
- Background service scheduling (deferred to data-sync phase)
## Motivation
A well-structured DI configuration provides:
- Modular, testable architecture with clear separation of concerns
- Strongly-typed configuration binding via IOptions<T> pattern
- Extension methods that encapsulate module-specific registration logic
- Clear dependency graph visible in Program.cs
## Acceptance Criteria
1. Solution builds successfully with `dotnet build`
2. All Options classes bind correctly from appsettings.json
3. Extension methods register services with appropriate lifetimes:
- Scoped: Database services, repositories
- Singleton: Configuration, HTTP clients
- Transient: Short-lived processors
4. Program.cs clearly shows module registration order
5. `openspec validate setup-solution-foundation --strict` passes
## Dependencies
- migrate-database-schema (provides JdeScoping.Database project)
## Risks
| Risk | Mitigation |
|------|------------|
| Circular dependencies | Extension methods register only their module's services |
| Configuration drift | Options classes map 1:1 with appsettings sections |
| Missing services at runtime | Startup validation via GetRequiredService checks |
## Related Specs
- `infrastructure` - DI registration and configuration binding patterns
@@ -0,0 +1,131 @@
# Infrastructure Specification
## Purpose
Define dependency injection registration patterns and configuration binding patterns for the .NET 10 solution infrastructure.
## ADDED Requirements
### Requirement: Service registration pattern
The system SHALL use extension methods on IServiceCollection to register module-specific services.
#### Inputs
- IServiceCollection services
- IConfiguration configuration
#### Outputs
- IServiceCollection (fluent return for chaining)
#### Business Rules
- Each module SHALL have one extension method (AddDataAccess, AddDataSync, AddAuth, AddExcelExport, AddSearchProcessing)
- Extension methods SHALL bind their module's Options class from configuration
- Extension methods SHALL register services with appropriate lifetimes:
- Scoped: Database connections, repositories, unit-of-work
- Singleton: Configuration options, HTTP clients, caching services
- Transient: Stateless processors, validators
- Extension methods SHALL return IServiceCollection for fluent chaining
#### Scenario: Module service registration
- **WHEN** Program.cs calls builder.Services.AddDataAccess(configuration)
- **THEN** DataAccessOptions is bound from the "DataAccess" configuration section
- **AND** ILotFinderRepository is registered with Scoped lifetime
- **AND** the method returns IServiceCollection for further chaining
#### Scenario: Service lifetime correctness
- **WHEN** a Scoped service is requested multiple times within the same HTTP request
- **THEN** the same instance is returned each time
- **AND** a new instance is created for the next HTTP request
#### Scenario: Chained registration
- **WHEN** Program.cs chains multiple extension methods
- **THEN** all modules are registered in the order called
- **AND** the final IServiceCollection contains all registered services
### Requirement: Configuration binding pattern
The system SHALL use IOptions<T> pattern to bind strongly-typed configuration from appsettings.json.
#### Inputs
- appsettings.json with named sections
- Options class with matching property names
#### Outputs
- IOptions<T> resolved from DI with bound values
#### Business Rules
- Each Options class SHALL define a static SectionName constant matching the JSON section
- Options classes SHALL use C# naming conventions (PascalCase properties)
- Configuration sections SHALL use matching PascalCase names
- Default values SHALL be defined in Options class properties
- Options classes SHALL be registered using services.Configure<T>(section)
#### Scenario: Configuration binding at startup
- **WHEN** the application starts with valid appsettings.json
- **THEN** IOptions<DataAccessOptions> resolves with values from the DataAccess section
- **AND** properties not specified in JSON use their default values
#### Scenario: Missing configuration section
- **WHEN** the application starts without a required configuration section
- **THEN** IOptions<T> resolves with all default property values
- **AND** no exception is thrown at startup
#### Scenario: Development override
- **WHEN** the application runs in Development environment
- **THEN** appsettings.Development.json values override appsettings.json values
- **AND** IOptions<DataAccessOptions>.Value.EnableDetailedLogging is true
### Requirement: Extension method organization
The system SHALL organize extension methods in the JdeScoping.Core project under an Extensions namespace.
#### Business Rules
- Extension methods SHALL be in namespace JdeScoping.Core.Extensions
- Each module SHALL have a dedicated static class: {Module}ServiceExtensions
- Extension method SHALL be named Add{Module}
- Files SHALL be located at: JdeScoping.Core/Extensions/{Module}ServiceExtensions.cs
#### Scenario: Extension method discovery
- **WHEN** a developer adds using JdeScoping.Core.Extensions
- **THEN** all AddXxx extension methods are available on IServiceCollection
- **AND** IntelliSense shows method documentation
### Requirement: Options class organization
The system SHALL organize Options classes in the JdeScoping.Core project under an Options namespace.
#### Business Rules
- Options classes SHALL be in namespace JdeScoping.Core.Options
- Class names SHALL follow pattern: {Module}Options
- SectionName constant SHALL match the JSON section name exactly
- Files SHALL be located at: JdeScoping.Core/Options/{Module}Options.cs
#### Scenario: Options class consistency
- **WHEN** DataAccessOptions is defined with SectionName = "DataAccess"
- **THEN** configuration.GetSection("DataAccess") returns the matching section
- **AND** services.Configure<DataAccessOptions>(section) binds all properties
## Migration Notes
| Legacy Pattern | New Pattern | Rationale |
|----------------|-------------|-----------|
| Static Config class | IOptions<T> injection | Testable, supports hot reload |
| Hardcoded values | appsettings.json | Environment-specific configuration |
| Constructor instantiation | DI container registration | Loose coupling, lifetime management |
| Web.config | appsettings.json + environment files | .NET Core standard |
@@ -0,0 +1,113 @@
# Tasks: Setup Solution Foundation
## Phase 1: Options Classes
- [x] Create DataAccessOptions class
- Location: `NEW/src/JdeScoping.Core/Options/DataAccessOptions.cs`
- Properties: CommandTimeoutSeconds, EnableDetailedLogging
- Validation: Verify binds from appsettings.json
- [x] Create DataSyncOptions class
- Location: `NEW/src/JdeScoping.Core/Options/DataSyncOptions.cs`
- Properties: MassRefreshCronSchedule, DailyRefreshCronSchedule, HourlyRefreshCronSchedule, MaxConcurrentUpdates
- Validation: Verify binds from appsettings.json
- [x] Create AuthOptions class
- Location: `NEW/src/JdeScoping.Core/Options/AuthOptions.cs`
- Properties: LdapUrl, LdapGroup, CookieExpirationMinutes
- Validation: Verify binds from appsettings.json
- [x] Create ExcelExportOptions class
- Location: `NEW/src/JdeScoping.Core/Options/ExcelExportOptions.cs`
- Properties: TempDirectory, MaxRowsPerSheet, DefaultDateFormat
- Validation: Verify binds from appsettings.json
- [x] Create SearchProcessingOptions class
- Location: `NEW/src/JdeScoping.Core/Options/SearchProcessingOptions.cs`
- Properties: PollingIntervalSeconds, MaxConcurrentSearches, SearchTimeoutMinutes
- Validation: Verify binds from appsettings.json
## Phase 2: Extension Methods
- [x] Create DataAccessServiceExtensions class
- Location: `NEW/src/JdeScoping.Core/Extensions/DataAccessServiceExtensions.cs`
- Method: AddDataAccess(IServiceCollection, IConfiguration)
- Registers: DataAccessOptions, placeholder ILotFinderRepository
- Validation: Services resolve without error
- [x] Create DataSyncServiceExtensions class
- Location: `NEW/src/JdeScoping.Core/Extensions/DataSyncServiceExtensions.cs`
- Method: AddDataSync(IServiceCollection, IConfiguration)
- Registers: DataSyncOptions, placeholder IUpdateProcessor
- Validation: Services resolve without error
- [x] Create AuthServiceExtensions class
- Location: `NEW/src/JdeScoping.Core/Extensions/AuthServiceExtensions.cs`
- Method: AddAuth(IServiceCollection, IConfiguration)
- Registers: AuthOptions, authentication services
- Validation: Services resolve without error
- [x] Create ExcelExportServiceExtensions class
- Location: `NEW/src/JdeScoping.Core/Extensions/ExcelExportServiceExtensions.cs`
- Method: AddExcelExport(IServiceCollection, IConfiguration)
- Registers: ExcelExportOptions, placeholder IExcelWriter
- Validation: Services resolve without error
- [x] Create SearchProcessingServiceExtensions class
- Location: `NEW/src/JdeScoping.Core/Extensions/SearchProcessingServiceExtensions.cs`
- Method: AddSearchProcessing(IServiceCollection, IConfiguration)
- Registers: SearchProcessingOptions, placeholder ISearchProcessor
- Validation: Services resolve without error
## Phase 3: Configuration Files
- [x] Update appsettings.json with all configuration sections
- Location: `NEW/src/JdeScoping.Host/appsettings.json`
- Sections: ConnectionStrings, DataAccess, DataSync, Auth, ExcelExport, SearchProcessing
- Validation: JSON parses without error
- [x] Create appsettings.Development.json with dev overrides
- Location: `NEW/src/JdeScoping.Host/appsettings.Development.json`
- Overrides: Local SQL Server connection, detailed logging, disabled sync schedules
- Validation: JSON parses without error
## Phase 4: Program.cs Update
- [x] Update Program.cs with service registration
- Location: `NEW/src/JdeScoping.Host/Program.cs`
- Call all AddXxx extension methods
- Add database migration at startup
- Validation: Application starts without DI errors
- [x] Add startup validation for critical services
- Location: `NEW/src/JdeScoping.Host/Program.cs`
- Validate: GetRequiredService for IOptions<T> classes
- Validation: Startup fails fast on misconfiguration
## Phase 5: Project References
- [x] Add NuGet packages to JdeScoping.Core
- Packages: Microsoft.Extensions.Options, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Configuration.Abstractions, Microsoft.Extensions.Configuration.Binder, Microsoft.Extensions.Options.ConfigurationExtensions
- Validation: `dotnet build` succeeds
- [x] Add NuGet packages to JdeScoping.Host
- Packages: Microsoft.Data.SqlClient, Dapper, EPPlus, DbUp-SqlServer, Quartz (or similar scheduler)
- Validation: `dotnet build` succeeds
- [x] Add project reference from Host to Core
- Reference: JdeScoping.Host references JdeScoping.Core
- Validation: `dotnet build` succeeds
## Phase 6: Verification
- [x] Run full solution build
- Command: `dotnet build NEW/JdeScoping.sln`
- Validation: Build succeeds with no errors
- [x] Verify configuration binding
- Test: Startup resolves IOptions<DataAccessOptions> etc.
- Validation: All options have expected default values
- [x] Run openspec validation
- Command: `openspec validate setup-solution-foundation --strict`
- Validation: No errors reported