using FastEndpoints; using FastEndpoints.Swagger; using GB5Shared.Addon; using GB5Shared.Connection; using GB5Shared.GOP.Qualifier; using GB5Shared.Telemetry; using GB5Shared.DateConverter; using static GB5Shared.DateConverter.GB5JsonOptions; using GB5Shared.DTO.Framework.CommonConfig; using GB5Shared.EntityHandler; using GB5Shared.EventLogPublish; using GB5Shared.GB5CommonFunction; using GB5Shared.GB5Library.Qualifier; using GB5Shared.GenerateAutoNumber; using GB5Shared.GOP.ExecuteAsync; using GB5Shared.PickListGenerator; using GB5Shared.PubSub.OutBox; using GB5Shared.QueryExecutor; using GB5Shared.Validation; using GB5Shared.WorkFlow.WorkFlowEngine; using GB5Shared.WorkFlow.WorkFlowRunTime; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Caching.Hybrid; using OpenTelemetry; using OpenTelemetry.Trace; using PayRollBLL.SAP.Config; using PayRollBLL.SAPPaySlip.Config; using PayRollDAL.CustomeCode.DailyAttendance; using PayRollDAL.CustomeCode.OULevelSetting; using AdminDAL.CustomCode.Period; using PayRollSL.Services; using PayRollDAL.DTO.Employee; using System.Data; using System.Reflection; using System.Text; using Microsoft.Data.SqlClient; using OpenTelemetry.Resources; using OpenTelemetry.Metrics; using Npgsql; using static GB5Shared.DTO.Framework.Enum.FrameworkEnumDTO; // ------------------------- // Bootstrap // ------------------------- var builder = WebApplication.CreateBuilder(args); // Register Dapper type handlers (DateOnly/DateTimeOffset) before any DB call // in the process — must win the race against hosted services / DI-resolved // QueryExecutor instances, since Dapper caches its per-SQL parameter mapping // for the lifetime of the process on first use. GB5DapperTypeHandlers.Register(); // ------------------------- // Console Encoding // ------------------------- Console.OutputEncoding = Encoding.UTF8; // ------------------------- // Configure Web Host & Port // ------------------------- // ── Port from config — change "AppPort" in appsettings.json to use any port ── var appPort = builder.Configuration.GetValue("AppPort"); builder.WebHost.UseUrls($"http://0.0.0.0:{appPort}"); // ------------------------- // Dapr & Controllers Setup // ------------------------- builder.Services.AddDaprClient(); builder.Services.AddControllers() .AddDapr() .AddJsonOptions(options => { options.JsonSerializerOptions.PropertyNamingPolicy = null; options.JsonSerializerOptions.DictionaryKeyPolicy = null; options.JsonSerializerOptions.AddGB5Converters(); }); builder.Services.Configure(options => { options.SuppressModelStateInvalidFilter = true; }); // ------------------------- // JSON Global (Minimal API / HttpContext) // ------------------------- builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.PropertyNamingPolicy = null; options.SerializerOptions.DictionaryKeyPolicy = null; options.SerializerOptions.AddGB5Converters(); }); // ------------------------- // Caching // ------------------------- #pragma warning disable EXTEXP0018 builder.Services.AddHybridCache(options => { options.DefaultEntryOptions = new HybridCacheEntryOptions(); options.DisableCompression = false; }); #pragma warning restore EXTEXP0018 builder.Services.AddMemoryCache(); builder.Services.AddDistributedMemoryCache(); // ------------------------- // CORS Policy // ------------------------- // ------------------------- // Application Services // ------------------------- 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.AddHttpClient(); // SAP IT Declaration integration — named HttpClient "sapItDeclaration" // Base URL is read from SapItDeclaration:BaseUrl. Credentials are injected // per-request inside SapApiClient via IOptions. var sapItDecBaseUrl = builder.Configuration["SapItDeclaration:BaseUrl"]; if (!string.IsNullOrWhiteSpace(sapItDecBaseUrl)) { builder.Services.AddHttpClient("sapItDeclaration", client => { client.BaseAddress = new Uri(sapItDecBaseUrl); client.DefaultRequestHeaders.Add("Accept", "application/json"); }); } else { builder.Services.AddHttpClient("sapItDeclaration"); } // SAP PaySlip integration — named HttpClient "sapPaySlip" // Only the base address and Accept header are set here. // Username, Password, and SapClient are injected per-request inside SapPaySlipApiClient // so credentials never sit in shared config or headers. var sapPaySlipBaseUrl = builder.Configuration["SapPaySlip:BaseUrl"]; if (!string.IsNullOrWhiteSpace(sapPaySlipBaseUrl)) { builder.Services.AddHttpClient("sapPaySlip", client => { client.BaseAddress = new Uri(sapPaySlipBaseUrl); // Accept: application/json — always required by the SAP OData service. client.DefaultRequestHeaders.Add("Accept", "application/json"); }); } else { // Register a placeholder so DI doesn't fail when the URL is not yet configured. builder.Services.AddHttpClient("sapPaySlip"); } // Workflow + Entity Pipeline builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(typeof(BaseEntityAppService<>), typeof(BaseEntityAppService<>)); builder.Services.AddScoped(); builder.Services.AddGB5QualifierEngine(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ------------------------- // Queue Processor + Handlers (PAYSLIP, PAYRUN) // ------------------------- builder.Services.AddHostedService(); // ------------------------- // Configuration Binding // ------------------------- builder.Services.Configure( builder.Configuration.GetSection("DefaultLanguage")); builder.Services.Configure( builder.Configuration.GetSection("Gb5SystemDTO")); // SAP IT Declaration — bind SapItDeclaration section for injection via IOptions builder.Services.Configure( builder.Configuration.GetSection(SapItDeclarationOptions.SectionName)); // SAP PaySlip — bind SapPaySlip section to SapPaySlipOptions for injection via IOptions builder.Services.Configure( builder.Configuration.GetSection(SapPaySlipOptions.SectionName)); // ✅ Read Gb5SystemDTO config var gb5Config = builder.Configuration .GetSection("Gb5SystemDTO") .Get(); // ✅ Resolve active DB type from appsettings DataBaseType value var activeDbType = (DatabaseType)gb5Config.DataBaseType; // ✅ Register DatabaseTypeConfig as singleton — injected into DAL builder.Services.AddSingleton(new DatabaseTypeConfig(activeDbType)); // ------------------------- // Kestrel // ------------------------- builder.Services.Configure(options => { options.AllowSynchronousIO = true; // Large batch endpoints (e.g. reassign attendance for all OUs/employees) can // run for several minutes. Set a generous request timeout so Kestrel does not // drop the connection before the work finishes. options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30); options.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(30); }); // -- OpenTelemetry (Tracing + Metrics) ---------------------------------------- builder.Services.AddGB5Telemetry(builder.Configuration, "GB5-PAYROLL"); // ------------------------- // Assembly Scan for DI // ------------------------- var payRollBLLAssembly = Assembly.Load("PayRollBLL"); var payRollDALAssembly = Assembly.Load("PayRollDAL"); builder.Services.Scan(scan => scan .FromAssemblies(payRollBLLAssembly, payRollDALAssembly) .AddClasses(c => c.Where(t => !t.IsAbstract && !t.IsInterface && t.GetMethod("$") == null)) .AsImplementedInterfaces() .WithScopedLifetime()); // ✅ IDbConnection — switches SQL Server or PostgreSQL based on DataBaseType builder.Services.AddScoped(sp => { // ✅ Use ActiveConnectionString — handles both old and new format var connStr = gb5Config.ActiveConnectionString; return activeDbType switch { DatabaseType.SQL => (IDbConnection)new SqlConnection(connStr), DatabaseType.POSTGRE => (IDbConnection)new NpgsqlConnection(connStr), _ => throw new NotSupportedException($"DataBaseType '{gb5Config.DataBaseType}' not supported.") }; }); // Npgsql 6+ strict mode rejects DateTime with Kind != Utc for timestamptz columns. // Must be set before any NpgsqlConnection is created. AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); // ------------------------- // FastEndpoints // ------------------------- builder.Services.AddFastEndpoints(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // PerquisitesBLL reuses AdminDAL's PeriodDAL; AdminDAL isn't part of the Scrutor scan above (PayRollBLL/DAL only). builder.Services.AddScoped(); // ------------------------- // AttachmentBulkImportAsynch — GB5Shared services not covered by the PayRollBLL/DAL scan above // ------------------------- builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // ------------------------- // Swagger // ------------------------- builder.Services.SwaggerDocument(o => { o.DocumentSettings = s => { s.Title = "GB5 PayRoll API"; s.Version = "v1"; s.Description = "GB5 PayRoll Microservice"; }; }); // ------------------------- // Build App // ------------------------- var app = builder.Build(); // ------------------------- // Middleware // ------------------------- app.UseCloudEvents(); app.UseMiddleware(); app.UseMiddleware(); app.UseFastEndpoints(c => { c.Serializer.Options.PropertyNamingPolicy = null; c.Serializer.Options.DictionaryKeyPolicy = null; c.Serializer.Options.AddGB5Converters(); }); app.MapControllers(); app.MapSubscribeHandler(); // ------------------------- // Swagger UI // ------------------------- app.UseOpenApi(); app.UseSwaggerUi(o => { o.Path = "/GB5Documentation"; o.DocumentPath = "/swagger/{documentName}/swagger.json"; o.TransformToExternalPath = (internalUiRoute, _) => "/prs" + internalUiRoute; }); // ------------------------- // Test Endpoint // ------------------------- app.MapGet("/", () => "Hello from GB5 PayRoll Service (.NET 9)"); // ------------------------- // Authorization // ------------------------- app.UseAuthorization(); // ------------------------- // Run // ------------------------- app.Run();