// =============================================================================
// 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;
}
}
}