// ============================================================================= // GB5 Framework — ReportEndpointBase & [ReportEndpoint] attribute // Namespace : FrameworkSL.ReportOrchestration // Purpose : Base class that every module report endpoint must inherit. // Enforces the standard contract (IReportEndpoint, defined in // FrameworkBLL) while giving modules full control over their SQL. // // Migration guide for module teams: // 1. Change endpoint base class to ReportEndpointBase. // 2. Decorate with [ReportEndpoint(ReportCode = "SALES001")]. // 3. Move data-fetch logic into GetReportDataAsync(). // 4. Remove any connection-string construction — use GetConnectionAsync(). // 5. Remove any export logic — the orchestrator handles it. // 6. Remove any pivot logic — DataPivotEngine is called by the orchestrator. // 7. Keep all SQL, joins, filtering, and business rules unchanged. // ============================================================================= using System.Data; using System.Runtime.CompilerServices; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.ReportOrchestration; using GB5Shared.Enums.ReportOrchestration; using FrameworkBLL.ReportOrchestration; namespace FrameworkSL.ReportOrchestration { // ========================================================================= // ReportEndpointAttribute — decorates each module report endpoint // ========================================================================= /// /// Decorates a module report endpoint to register it with the orchestrator /// and declare its preferred datasource. /// Apply once per endpoint class. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public sealed class ReportEndpointAttribute : Attribute { /// MREPORT.REPORTCODE this endpoint serves. Must match exactly. public string ReportCode { get; init; } = string.Empty; /// /// Default datasource preference. Overridden at runtime by MREPORTCONFIG. /// Declare here as documentation; the resolved source comes from the orchestrator. /// public DataSourceType PreferredDataSource { get; init; } = DataSourceType.Oltp; /// /// When true, this endpoint can be called from a SysJob worker (async execution). /// Set to false for endpoints returning real-time-only data (live dashboards). /// public bool SupportsAsync { get; init; } = true; } // ========================================================================= // ReportEndpointBase — base class handling all infrastructure concerns // ========================================================================= /// /// Abstract base class for all module report endpoints. /// Implements (defined in FrameworkBLL) so /// the orchestrator can call it without depending on FrameworkSL. /// Module teams override only. /// public abstract class ReportEndpointBase : IReportEndpoint { private readonly IReportConnectionResolver _connectionResolver; protected ReportEndpointBase(IReportConnectionResolver connectionResolver) { _connectionResolver = connectionResolver; } // --------------------------------------------------------------------- // MODULE TEAMS IMPLEMENT THIS METHOD ONLY // --------------------------------------------------------------------- /// public abstract IAsyncEnumerable> GetReportDataAsync( ReportExecutionContext context, IDbConnection connection, CancellationToken cancellationToken = default); // --------------------------------------------------------------------- // INFRASTRUCTURE HELPERS // --------------------------------------------------------------------- /// /// Resolves and opens a connection for the given datasource. /// Use only when a second connection is needed (rare); prefer the /// connection injected into . /// protected async Task GetConnectionAsync( ReportExecutionContext context, DataSourceType dataSource, CancellationToken cancellationToken = default) { var connection = await _connectionResolver .ResolveConnectionAsync(context.LoginDTO, dataSource, cancellationToken) .ConfigureAwait(false); await ((System.Data.Common.DbConnection)connection) .OpenAsync(cancellationToken).ConfigureAwait(false); return connection; } /// /// Extracts a typed parameter value from context.Parameters. /// Returns when the key is absent /// or the value cannot be converted to . /// protected static T? GetParameter( ReportExecutionContext context, string key, T? defaultValue = default) { if (!context.Parameters.TryGetValue(key, out var raw) || raw is null) return defaultValue; try { return (T)Convert.ChangeType(raw, typeof(T)); } catch { return defaultValue; } } /// /// Builds a Dapper parameter object pre-populated with the standard /// identity fields (ClientId, WorkOUId, RoleId, WorkPeriodId, WorkDate). /// Add module-specific parameters to the returned object. /// protected static Dapper.DynamicParameters BuildBaseParameters( ReportExecutionContext context) { var p = new Dapper.DynamicParameters(); p.Add("ClientId", context.LoginDTO.ClientId); p.Add("WorkOUId", context.LoginDTO.WorkOUId); p.Add("RoleId", context.LoginDTO.RoleId); p.Add("WorkPeriodId", context.LoginDTO.WorkPeriodId); p.Add("WorkDate", context.LoginDTO.WorkDate); return p; } } }