using System.Text.Json.Serialization; using ERPCore.Infra.Auth; using ERPCore.Infra.Auth.AuthHex; using ERPCore.Infra.Persistence; using ERPCore.Infra.Storage; using ERPCore.Infra.UoW; using ERPCore.Repositories; using ERPCore.Repositories.Interfaces; using ERPCore.Services; using ERPCore.Services.Interfaces; using ERPCore.Services.Auth; using ERPCore.Services.Hrm; using ERPCore.Services.Interfaces; using ERPCore.Services.Production; using ERPCore.Services.Stock; using ERPCore.System.Errors; using Microsoft.AspNetCore.Authentication; using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi; using Serilog; var builder = WebApplication.CreateBuilder(args); // Serilog (file sink) — reads levels from configuration, plus a rolling daily file. builder.Host.UseSerilog((ctx, cfg) => cfg .ReadFrom.Configuration(ctx.Configuration) .WriteTo.File("logs/erpcore-.log", rollingInterval: RollingInterval.Day)); // Controllers + JSON: serialize enums as their string names (docs/11 §8, camelCase). builder.Services.AddControllers() .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())); // EF Core + PostgreSQL builder.Services.AddDbContext(o => o.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); // ProblemDetails (RFC 7807) + domain-exception mapping builder.Services.AddProblemDetails(); builder.Services.AddExceptionHandler(); // Auth: validate external AuthHex RS256 tokens + ERP door policy (docs/10 A.4) builder.Services.AddErpJwtAuth(builder.Configuration); // AuthController proxy → AuthHex (docs/11 §2.0) builder.Services.AddHttpClient(c => { var baseUrl = builder.Configuration["AuthHex:BaseUrl"] ?? throw new InvalidOperationException("AuthHex:BaseUrl is not configured."); c.BaseAddress = new Uri(baseUrl); }); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Current-user (audit actor). AuthHex has no sub/nameid → a claims transformation // JIT-provisions a local shadow user and injects the local `int` id as `nameid`. builder.Services.AddHttpContextAccessor(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Unit of work + generic repository base builder.Services.AddScoped(); builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>)); // Master-data services (docs/11 §2) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // RBAC / sidebar: Role (shadow of AuthHex) + Permission assignment + user management builder.Services.AddScoped(); builder.Services.AddScoped(); // Cross-cutting + procurement services (docs/11 §3) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Stock core + goods receipt (docs/11 §4–5) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Stock transactions + reference data (docs/11 §5–6) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Cross-cutting: audit trail + GL-ready journal read access (docs/11 §6 / FR-X-02, FR-STK-13) builder.Services.AddScoped(); // Dashboard aggregate stats (cross-domain read: stock, GRN, procurement) builder.Services.AddScoped(); // HRM (docs/13-BACKEND-HRM-API.md): org masters, employee core, staff documents builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // HRM: Leave (docs/13-BACKEND-HRM-API.md §5) — LeaveType/LeaveBalance built before // LeaveRequest since approval increments balances; Attendance depends on LeaveRequest. builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // HRM: Attendance (docs/13-BACKEND-HRM-API.md §4) builder.Services.AddScoped(); builder.Services.AddScoped(); // HRM: Payroll (docs/13-BACKEND-HRM-API.md §6) — masters/settings before the // calculation service, which composes them; PayrollRunService orchestrates last. builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // HRM: Reports (docs/13-BACKEND-HRM-API.md §6) — read-only, no new entities builder.Services.AddScoped(); // Manufacturing / Production Lines (docs/30-BACKEND-PHASE2.md Part A). Templates stand // alone; runs consume FifoCostingService for all stock movement. ProductionGraphValidator // is deliberately unregistered — it is a pure static algorithm, not an injected service. builder.Services.AddScoped(); builder.Services.AddScoped(); // Health checks (EF Core DB) builder.Services.AddHealthChecks().AddDbContextCheck(); // Swagger / OpenAPI (Swashbuckle v10 → OpenAPI 3.1) builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(o => { o.SwaggerDoc("v1", new OpenApiInfo { Title = "ERPCore API", Version = "v1" }); o.CustomSchemaIds(t => t.FullName!.Replace("+", ".")); }); var app = builder.Build(); // Seed configurable reference data (reason codes) idempotently at startup. using (var scope = app.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); await DataSeeder.SeedAsync(db); } app.UseSerilogRequestLogging(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseExceptionHandler(); // -> DomainExceptionHandler / ProblemDetails app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.MapHealthChecks("/health"); app.Run();