using AccountsDAL.CustomCode.AccountReports.AccountLedger; using AccountsDAL.DTO.AccountsReports; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; namespace AccountsBLL.AccountReports.AccountLedger { public class AccountLedgerReportBLL : IAccountLedgerReportBLL { private const int DEFAULT_MAX_RESULT_CAP = 500; // "General ledger causes load issues" — legacy's only answer was a separate, hardcoded-email // async reimplementation (see plan). Here: if the caller hasn't narrowed the request to specific // accounts/a group/a control account, cap how many distinct accounts a single interactive // (paged) call may span before rejecting it outright. The streaming export path // (GetAccountLedgerDetailStream) is exempt — it's the documented answer for legitimate wide pulls. private const int MAX_UNSCOPED_ACCOUNT_COUNT = 200; private readonly IAccountLedgerReportDAL _accountLedgerReportDAL; private readonly IAccountReportAccessControlBLL _accessControlBLL; public AccountLedgerReportBLL(IAccountLedgerReportDAL accountLedgerReportDAL, IAccountReportAccessControlBLL accessControlBLL) { _accountLedgerReportDAL = accountLedgerReportDAL; _accessControlBLL = accessControlBLL; } public async Task GetAccountLedgerDetailReport( int firstNumber, int maxResult, CriteriaDTO criteriaDTO, List sortBy, int menuId, LoginDTO loginDTO) { try { GB5Trace.Step("parse-criteria-account-ledger-detail", new { firstNumber, maxResult, menuId }); var criteria = AccountReportCriteriaParser.Parse(criteriaDTO, loginDTO); var cappedMaxResult = Math.Min(maxResult, DEFAULT_MAX_RESULT_CAP); await _accessControlBLL.EnforceAsync(criteria, cappedMaxResult, menuId, loginDTO).ConfigureAwait(false); var isScopedToAccounts = criteria.VoucherDetailAccountIds.Count > 0 || criteria.AccountGroupId != 0 || criteria.ControlAccountId != 0; if (!isScopedToAccounts) { GB5Trace.Step("check-account-count-cap", new { firstNumber, cappedMaxResult }); var distinctAccountCount = await _accountLedgerReportDAL .GetDistinctAccountCount(criteria, loginDTO).ConfigureAwait(false); if (distinctAccountCount > MAX_UNSCOPED_ACCOUNT_COUNT) throw new InvalidOperationException(ErrorResponse.TooManyAccountsMessage); } GB5Trace.Step("query-account-ledger-detail", new { firstNumber, cappedMaxResult }); return await _accountLedgerReportDAL .GetAccountLedgerDetailReport(firstNumber, cappedMaxResult, criteria, sortBy, loginDTO) .ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("account-ledger-detail-failed", ex); throw; } } public IAsyncEnumerable GetAccountLedgerDetailStream( CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct) { var criteria = AccountReportCriteriaParser.Parse(criteriaDTO, loginDTO); return _accountLedgerReportDAL.GetAccountLedgerDetailStream(criteria, loginDTO, ct); } } }