// ============================================================================= // GB5 Framework — IQueryOrchestrator // Namespace : FrameworkBLL.ReportOrchestration // Purpose : Central entry point for ALL report and analysis execution. // Orchestrates: datasource resolution, async decision, URI call // (standard reports) or dynamic SQL execution (analysis reports), // pivot transformation, export, caching, and SysJob submission. // // What callers do: // 1. Build a ReportExecutionContext (identity + parameters + call type). // 2. Call IQueryOrchestrator.ExecuteReportAsync(context). // 3. Inspect ReportExecutionResult.ResultType: // SyncCompleted → stream GridData or ExportStream to browser. // AsyncSubmitted → return SysJobId; Angular shows My Reports inbox. // Failed → return error response with CorrelationId. // // What callers do NOT do: // - Resolve DB connections. // - Decide sync vs async. // - Call module endpoints directly. // - Generate export files. // - Submit SysJobs. // // Lifetime: Scoped (one instance per HTTP request). // ============================================================================= using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.ReportOrchestration; using GB5Shared.Enums.ReportOrchestration; namespace FrameworkBLL.ReportOrchestration { /// /// Central orchestrator for report and analysis execution. /// All module API endpoints and background job workers call this interface; /// no module should construct DB connections or resolve datasources directly /// for reporting purposes. /// public interface IQueryOrchestrator { // ===================================================================== // PRIMARY ENTRY POINT // ===================================================================== /// /// Executes a report or analysis as described by /// and returns a unified result. /// /// /// Pipeline stages (in order): /// /// Load MREPORTCONFIG / MANALYSISWORKSPACE metadata. /// Validate requested ExportFormat against ALLOWEDEXPORTFORMATS. /// Evaluate datasource rules (Auto → concrete DataSourceType). /// Resolve IDbConnection via IReportConnectionResolver. /// Evaluate async decision (AsyncMode + DateRangeDays + policy). /// /// If sync: execute (URI call or dynamic SQL), optionally pivot, /// export, optionally cache, return SyncCompleted result. /// /// /// If async: submit SysJob with resolved context serialised as /// TSYSJOB.PARAMETERS, return AsyncSubmitted result with SysJobId. /// /// /// /// Performance notes: /// /// MREPORTCONFIG is cached within the request (Scoped resolver). /// Result data is cached in IDistributedCache when /// MREPORTCONFIG.CACHEDURATIONMINUTES > 0. /// NOLOCK hints are applied automatically when /// MREPORTCONFIG.ISNOLOCKALLOWED = true and provider = SQL Server. /// CancellationToken from context is propagated to all async ops. /// /// /// /// Fully populated execution context. At minimum must carry LoginDTO, /// ReportId or AnalysisId, CallType, Parameters, RequestedFormat, /// and CancellationToken. /// /// /// A with ResultType indicating /// how the caller should respond to the browser. /// Never throws — exceptions are caught and returned as /// with a safe error message. /// Task ExecuteReportAsync(ReportExecutionContext context); // ===================================================================== // ASYNC (SYSJOB WORKER) ENTRY POINT // ===================================================================== /// /// Called by the SysJob worker to execute a previously queued async /// report. Deserialises the context from TSYSJOB.PARAMETERS, executes, /// generates the export file, saves it, and updates TSYSJOB.RESULTLOCATION. /// /// /// This overload always executes synchronously relative to the caller /// (the worker thread). It does not re-submit as another SysJob. /// TSYSJOB.RUNSTATUS is managed by the SysJob framework — the worker /// sets it to Running before calling this, and to Completed/Failed after. /// /// /// The TSYSJOB.SYSJOBID being executed. Used to load parameters /// and update result location on completion. /// /// /// The session context reconstructed by the SysJob worker from the /// TSYSJOB row (ClientId, UserId, ServerConfigId etc.). /// /// /// Cancellation token from the worker; triggered on job timeout or /// explicit cancellation from the My Reports inbox. /// /// /// The file path/URL stored in TSYSJOB.RESULTLOCATION on success. /// Throws on unrecoverable failure — worker sets RUNSTATUS = Failed. /// Task ExecuteAsyncJobAsync( int sysJobId, LoginDTO loginDTO, CancellationToken cancellationToken = default); // ===================================================================== // CONTEXT BUILDER — fluent helper for callers // ===================================================================== /// /// Fluent builder to construct a /// for a standard report (MMenu REPORTTYPE 1 or 2). /// IReportContextBuilder ForStandardReport( LoginDTO loginDTO, int reportId, int viewId = -1); /// /// Fluent builder to construct a /// for an analysis report (MMenu REPORTTYPE 0 or 3). /// IReportContextBuilder ForAnalysisReport( LoginDTO loginDTO, int analysisId, int queryId = -1); } // ========================================================================= // IReportContextBuilder — fluent builder interface // ========================================================================= /// /// Fluent builder for . /// Obtained via or /// . /// public interface IReportContextBuilder { /// /// Adds filter parameters. Accepts an anonymous object, a dictionary, /// or any object serialisable to key-value pairs. /// Existing parameters are merged (last-write-wins per key). /// IReportContextBuilder WithParameters(object parameters); /// /// Sets the requested export format. /// Validated against MREPORTCONFIG.ALLOWEDEXPORTFORMATS at execution time. /// Default: Grid. /// IReportContextBuilder WithFormat(ExportFormat format); /// /// Extracts and sets FromDate / ToDate from the parameters dictionary /// for DateRangeDays calculation. Call after WithParameters. /// Keys default to "FromDate" and "ToDate" — override if your report /// uses different parameter names. /// IReportContextBuilder WithDateRange( string fromDateKey = "FromDate", string toDateKey = "ToDate"); /// /// Sets the CancellationToken from the HTTP request. /// Always call this: .WithCancellation(HttpContext.RequestAborted) /// IReportContextBuilder WithCancellation(CancellationToken cancellationToken); /// /// Explicitly overrides the DataSourceType, bypassing MREPORTCONFIG /// and rule evaluation. Use sparingly — prefer metadata-driven routing. /// Intended for admin/debug endpoints only. /// IReportContextBuilder WithDataSourceOverride(DataSourceType dataSource); /// Constructs and returns the immutable . ReportExecutionContext Build(); } // ========================================================================= // IReportEndpoint — contract every module report endpoint must satisfy // ========================================================================= /// /// Contract for a module report endpoint. Implemented by module classes /// that inherit FrameworkSL.ReportOrchestration.ReportEndpointBase. /// The orchestrator calls after resolving /// the connection and making the async/sync decision. /// public interface IReportEndpoint { /// /// Fetches the flat report data rows for the given request context. /// The connection is already open when this method is called. /// Do not close or dispose it — the orchestrator manages lifetime. /// System.Collections.Generic.IAsyncEnumerable> GetReportDataAsync( ReportExecutionContext context, System.Data.IDbConnection connection, CancellationToken cancellationToken = default); } // ========================================================================= // IReportEndpointRegistry — discovery and lookup of module endpoints // ========================================================================= /// /// Registry of all module report endpoint implementations. /// Singleton — populated at startup by scanning module assemblies. /// Used by to locate the correct endpoint. /// public interface IReportEndpointRegistry { /// /// Locates the for the given report code. /// Returns null when no endpoint is registered for this code. /// IReportEndpoint? Resolve(string reportCode); /// Returns all registered report codes. System.Collections.Generic.IReadOnlyList GetRegisteredReportCodes(); } // ========================================================================= // IAsyncDecisionEngine — injectable async/sync decision logic // ========================================================================= /// /// Evaluates whether a report request should execute synchronously or be /// submitted as a SysJob, based on AsyncMode, DateRangeDays, and policy. /// Separated from the orchestrator so policy rules can be swapped/extended /// without changing orchestration logic. /// Registered as Scoped. /// public interface IAsyncDecisionEngine { /// /// Returns true if the request should be submitted as a SysJob. /// /// /// Decision logic by AsyncMode: /// /// Auto: true when DateRangeDays > AsyncThresholdDays. /// AlwaysAsync: always true. /// AlwaysSync: always false. /// PolicyBased: evaluates MREPORTPOLICY rules for /// ClientId, RoleId, ModuleId, time-of-day. Falls back to /// Auto if no policy matches. /// /// /// /// Must have ReportConfig set (provides AsyncMode + AsyncThresholdDays). /// /// Cancellation token for async operation. Task ShouldRunAsyncAsync( ReportExecutionContext context, CancellationToken cancellationToken = default); } }