// ============================================================================= // GB5 Framework — ReportOrchestrationServiceExtensions // Namespace : FrameworkSL.ReportOrchestration // Purpose : DI registration for all orchestration services. // Call AddReportOrchestration() once in Program.cs of each // module API and once in the Framework API. // // ============================================================================= // MIGRATION STANDARD — READ THIS BEFORE MIGRATING A REPORT // ============================================================================= // // STEP 1: Add MREPORTCONFIG row for your report // - DATASOURCETYPE: 0=OLTP (default), 1=ReportDB, 2=ArchiveDB, 3=Auto // - ASYNCMODE: 0=Auto (period-based), 1=AlwaysAsync, 2=AlwaysSync // - ASYNCTHRESHOLDDAYS: 90 (default) — adjust per report complexity // - QUERYTIMEOUTSECONDS: 30 sync / 300 async (defaults) // - PIVOTENGINE: 0=None, 1=Backend, 2=Frontend // - ISNOLOCKALLOWED: 0 (true) for ReportDB/Archive MSSQL reads // - ALLOWEDEXPORTFORMATS: "0,1,2,3" = Grid,PDF,Excel,CSV (default) // - REPORTCATEGORY: 0=Operational, 1=Analytical, 2=KPI, 3=Archive // // STEP 2: Add MREPORTDATASOURCERULE rows (optional, for Auto datasource) // Example: DateRangeDays > 365 → ArchiveDB // RULETYPE=0, THRESHOLDVALUE=365, TARGETDATASOURCE=2, RULEPRIORITY=10 // // STEP 3: Migrate the endpoint class // BEFORE (typical legacy pattern): // ───────────────────────────────── // public class SalesReportEndpoint : BaseEndpoint // { // public async Task GetReport([FromBody] LoginDTO loginDTO) // { // string connStr = ConfigHelper.GetConnectionString(loginDTO.ServerConfigId); // using var conn = new SqlConnection(connStr); // var data = await conn.QueryAsync<...>(Sql, new { loginDTO.ClientId, ... }); // return Ok(data); // } // } // // AFTER (orchestrated pattern): // ───────────────────────────── // [ReportEndpoint(ReportCode = "SALES001", PreferredDataSource = DataSourceType.ReportDb)] // public class SalesReportEndpoint : ReportEndpointBase // { // public SalesReportEndpoint(IReportConnectionResolver resolver) : base(resolver) { } // // public override async IAsyncEnumerable> // GetReportDataAsync( // ReportExecutionContext context, // IDbConnection connection, // [EnumeratorCancellation] CancellationToken ct = default) // { // var p = BuildBaseParameters(context); // p.Add("FromDate", GetParameter(context, "FromDate")); // p.Add("ToDate", GetParameter(context, "ToDate")); // // await foreach (var row in connection.QueryUnbufferedAsync>( // Sql, p, commandTimeout: context.QueryTimeoutSeconds).WithCancellation(ct)) // { // yield return row; // } // } // // private const string Sql = @" // SELECT I.INVOICENO, I.INVOICEDATE, C.CUSTOMERNAME, ... // FROM TINVOICE I // JOIN MCUSTOMER C ON C.CUSTOMERID = I.CUSTOMERID // WHERE I.CLIENTID = @ClientId // AND I.INVOICEDATE BETWEEN @FromDate AND @ToDate"; // } // // STEP 4: Remove from the endpoint // ✗ Connection string construction // ✗ SqlConnection / NpgsqlConnection instantiation // ✗ Export calls (PivotExcelExport, PivotPdfExport, CsvExporter) // ✗ DataPivotEngine calls // ✗ SysJob submission // // STEP 5: Keep in the endpoint // ✓ All SQL (unchanged) // ✓ All business parameter extraction // ✓ Any pre/post data processing specific to this report // // ============================================================================= using System.Reflection; using FrameworkBLL.ReportOrchestration; using GB5Shared.DTO.Framework.Login; using GB5Shared.Enums.ReportOrchestration; using Microsoft.Extensions.DependencyInjection; namespace FrameworkSL.ReportOrchestration { /// /// DI registration for the report orchestration infrastructure. /// Call once per module API and once in the Framework API. /// public static class ReportOrchestrationServiceExtensions { /// /// Registers all orchestration services with appropriate lifetimes. /// /// The service collection. /// /// One or more assemblies containing /// implementations for this module. Pass typeof(YourEndpoint).Assembly. /// The registry scans these for . /// public static IServiceCollection AddReportOrchestration( this IServiceCollection services, params Assembly[] reportAssemblies) { // ----------------------------------------------------------------- // Singleton: registry and configuration (built once at startup) // ----------------------------------------------------------------- services.AddSingleton(sp => { var logger = sp.GetRequiredService>(); var registry = new ReportEndpointRegistry(sp, logger); foreach (var assembly in reportAssemblies) registry.ScanAssembly(assembly); return registry; }); // ----------------------------------------------------------------- // Scoped: one instance per HTTP request // ----------------------------------------------------------------- // Connection resolution — reads MSERVERCONFIG via scoped cache services.AddScoped(); services.AddScoped(); // Orchestration pipeline services.AddScoped(); services.AddScoped(); services.AddScoped(); // ----------------------------------------------------------------- // Register each discovered ReportEndpointBase as Scoped // (Scoped so they can inject Scoped services like DAL, BLL etc.) // ----------------------------------------------------------------- foreach (var assembly in reportAssemblies) { var endpointTypes = assembly.GetTypes() .Where(t => !t.IsAbstract && t.IsClass && t.IsSubclassOf(typeof(ReportEndpointBase)) && t.GetCustomAttributes( typeof(ReportEndpointAttribute), true).Length > 0); foreach (var type in endpointTypes) { services.AddScoped(type); services.AddScoped( typeof(IReportEndpoint), sp => sp.GetRequiredService(type)); } } return services; } } // ========================================================================= // ReportRequestDTO — the standard request body for all report HTTP actions // ========================================================================= /// /// Standard request body posted by the Angular frontend for any report call. /// The module's HTTP action method receives this and passes it to the /// orchestrator builder. /// public sealed class ReportRequestDTO { /// The authenticated session. public LoginDTO LoginDTO { get; init; } = null!; /// MREPORT.REPORTID. -1 for analysis reports (use AnalysisId). public int ReportId { get; init; } = -1; /// MANALYSIS.ANALYSISID. -1 for standard reports. public int AnalysisId { get; init; } = -1; /// MREPORTVIEW.REPORTVIEWID. -1 = use default view. public int ViewId { get; init; } = -1; /// Report-specific filter parameters (FromDate, ToDate, etc.). public Dictionary Parameters { get; init; } = new(); /// The export format requested by the user. Default: Grid. public ExportFormat ExportFormat { get; init; } = ExportFormat.Grid; } }