diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 442c2fd..4d09e8a 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -13,104 +13,8 @@ namespace EstateReportingAPI.BusinessLogic{ using Merchant = Models.Merchant; using Operator = Models.Operator; - public class FeeTransactionProjection - { - public MerchantSettlementFee Fee { get; set; } - public Transaction Txn { get; set; } - } - - public static class ReportingManagerExtensions{ - public static IQueryable ApplyMerchantFilter( - this IQueryable query, - EstateManagementContext context, - List merchantIds) - { - if (merchantIds == null || merchantIds.Count == 0) - return query; - - return from q in query - join m in context.Merchants - on q.Txn.MerchantId equals m.MerchantId - where merchantIds.Contains(m.MerchantReportingId) - select q; - } - - public static IQueryable ApplyOperatorFilter( - this IQueryable query, - EstateManagementContext context, - List operatorIds) - { - if (operatorIds == null || operatorIds.Count == 0) - return query; - - return from q in query - join o in context.Operators - on q.Txn.OperatorId equals o.OperatorId - where operatorIds.Contains(o.OperatorReportingId) - select q; - } - - public static IQueryable ApplyProductFilter( - this IQueryable query, - EstateManagementContext context, - List productIds) - { - if (productIds == null || productIds.Count == 0) - return query; - - return from q in query - join cp in context.ContractProducts - on q.Txn.ContractProductId equals cp.ContractProductId - where productIds.Contains(cp.ContractProductReportingId) - select q; - } - public static IQueryable ApplyProductGrouping(this IQueryable fees, - EstateManagementContext context) - { - return from f in fees - join cp in context.ContractProducts on f.Txn.ContractProductId equals cp.ContractProductId - join c in context.Contracts on cp.ContractId equals c.ContractId - join op in context.Operators on c.OperatorId equals op.OperatorId - group f by new { op.Name, cp.ProductName } into g - select new UnsettledFee - { - DimensionName = $"{g.Key.Name} - {g.Key.ProductName}", - FeesValue = g.Sum(x => x.Fee.CalculatedValue), - FeesCount = g.Count() - }; - } - - public static IQueryable ApplyMerchantGrouping(this IQueryable fees, - EstateManagementContext context) - { - return from f in fees - join merchant in context.Merchants on f.Fee.MerchantId equals merchant.MerchantId - group f by merchant.Name into g - select new UnsettledFee - { - DimensionName = g.Key, - FeesValue = g.Sum(x => x.Fee.CalculatedValue), - FeesCount = g.Count() - }; - } - - public static IQueryable ApplyOperatorGrouping(this IQueryable fees, - EstateManagementContext context) - { - return from f in fees - join op in context.Operators on f.Txn.OperatorId equals op.OperatorId - group f by op.Name into g - select new UnsettledFee - { - DimensionName = g.Key, - FeesValue = g.Sum(x => x.Fee.CalculatedValue), - FeesCount = g.Count() - }; - } - } - - public class ReportingManager : IReportingManager{ + public partial class ReportingManager : IReportingManager{ private readonly IDbContextResolver Resolver; @@ -126,41 +30,6 @@ public ReportingManager(IDbContextResolver resolver) { #region Methods - - private IQueryable BuildUnsettledFeesQuery( - EstateManagementContext context, - DateTime startDate, - DateTime endDate) - { - return from merchantSettlementFee in context.MerchantSettlementFees - join transaction in context.Transactions - on merchantSettlementFee.TransactionId equals transaction.TransactionId - where merchantSettlementFee.FeeCalculatedDateTime.Date >= startDate && - merchantSettlementFee.FeeCalculatedDateTime.Date <= endDate - select new FeeTransactionProjection { Fee = merchantSettlementFee, Txn = transaction }; - } - - public async Task> GetUnsettledFees(Guid estateId, DateTime startDate, DateTime endDate, List merchantIds, List operatorIds, List productIds, GroupByOption? groupByOption, CancellationToken cancellationToken){ - - using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString()); - await using EstateManagementContext context = resolvedContext.Context; - - IQueryable query = BuildUnsettledFeesQuery(context, startDate, endDate) - .ApplyMerchantFilter(context, merchantIds) - .ApplyOperatorFilter(context, operatorIds) - .ApplyProductFilter(context, productIds); - - // Perform grouping - IQueryable groupedQuery = groupByOption switch - { - GroupByOption.Merchant => query.ApplyMerchantGrouping(context), - GroupByOption.Operator => query.ApplyOperatorGrouping(context), - GroupByOption.Product => query.ApplyProductGrouping(context), - }; - return await groupedQuery.ToListAsync(cancellationToken); - - } - public async Task> GetCalendarComparisonDates(Guid estateId, CancellationToken cancellationToken){ using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; @@ -819,107 +688,7 @@ public async Task GetOperatorPerformance(Guid estateId, DateTime co return response; } - - public async Task> TransactionSearch(Guid estateId, TransactionSearchRequest searchRequest, PagingRequest pagingRequest, SortingRequest sortingRequest, CancellationToken cancellationToken){ - // Base query before any filtering is added - using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString()); - await using EstateManagementContext context = resolvedContext.Context; - - var mainQuery = (from txn in context.Transactions - join merchant in context.Merchants on txn.MerchantId equals merchant.MerchantId - join @operator in context.Operators on txn.OperatorId equals @operator.OperatorId - join product in context.ContractProducts on txn.ContractProductId equals product.ContractProductId - where txn.TransactionDate == searchRequest.QueryDate.Date - select new - { - Transaction = txn, - Merchant = merchant, - Operator = @operator, - Product = product - }).AsQueryable(); - - // Now apply the filtering - if (searchRequest.Operators != null && searchRequest.Operators.Any()) - { - mainQuery = mainQuery.Where(m => searchRequest.Operators.Contains(m.Operator.OperatorReportingId)); - } - - if (searchRequest.Merchants != null && searchRequest.Merchants.Any()) - { - mainQuery = mainQuery.Where(m => searchRequest.Merchants.Contains(m.Merchant.MerchantReportingId)); - } - - if (searchRequest.ValueRange != null) - { - mainQuery = mainQuery.Where(m => m.Transaction.TransactionAmount >= searchRequest.ValueRange.StartValue && - m.Transaction.TransactionAmount <= searchRequest.ValueRange.EndValue); - } - - if (String.IsNullOrEmpty(searchRequest.AuthCode) == false) - { - mainQuery = mainQuery.Where(m => m.Transaction.AuthorisationCode == searchRequest.AuthCode); - } - - if (String.IsNullOrEmpty(searchRequest.ResponseCode) == false) - { - mainQuery = mainQuery.Where(m => m.Transaction.ResponseCode == searchRequest.ResponseCode); - } - - if (String.IsNullOrEmpty(searchRequest.TransactionNumber) == false) - { - mainQuery = mainQuery.Where(m => m.Transaction.TransactionNumber == searchRequest.TransactionNumber); - } - - Int32 skipCount = 0; - if (pagingRequest.Page > 1) - { - skipCount = (pagingRequest.Page - 1) * pagingRequest.PageSize; - } - - if (sortingRequest != null) - { - // Handle order by here, cant think of a better way of achieving this - mainQuery = (sortingRequest.SortDirection, sortingRequest.SortField) switch - { - (SortDirection.Ascending, SortField.MerchantName) => mainQuery.OrderBy(m => m.Merchant.Name), - (SortDirection.Ascending, SortField.OperatorName) => mainQuery.OrderBy(m => m.Operator.Name), - (SortDirection.Ascending, SortField.TransactionAmount) => mainQuery.OrderBy(m => m.Transaction.TransactionAmount), - (SortDirection.Descending, SortField.MerchantName) => mainQuery.OrderByDescending(m => m.Merchant.Name), - (SortDirection.Descending, SortField.OperatorName) => mainQuery.OrderByDescending(m => m.Operator.Name), - (SortDirection.Descending, SortField.TransactionAmount) => mainQuery.OrderByDescending(m => m.Transaction.TransactionAmount), - _ => mainQuery.OrderByDescending(m => m.Transaction.TransactionDateTime) - }; - } - - var queryResults = await mainQuery.Skip(skipCount).Take(pagingRequest.PageSize) - .ToListAsync(cancellationToken); - - List results = new List(); - - queryResults.ForEach(qr => - { - results.Add(new TransactionResult - { - MerchantReportingId = qr.Merchant.MerchantReportingId, - ResponseCode = qr.Transaction.ResponseCode, - IsAuthorised = qr.Transaction.IsAuthorised, - MerchantName = qr.Merchant.Name, - OperatorName = qr.Operator.Name, - OperatorReportingId = qr.Operator.OperatorReportingId, - Product = qr.Product.ProductName, - ProductReportingId = qr.Product.ContractProductReportingId, - ResponseMessage = qr.Transaction.ResponseMessage, - TransactionDateTime = qr.Transaction.TransactionDateTime, - TransactionId = qr.Transaction.TransactionId, - TransactionReportingId = qr.Transaction.TransactionReportingId, - TransactionSource = qr.Transaction.TransactionSource.ToString(), // TODO: Name for this - TransactionAmount = qr.Transaction.TransactionAmount - }); - }); - - return results; - } - + public async Task> GetMerchants(Guid estateId, CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString()); diff --git a/EstateReportingAPI.BusinessLogic/ReportingManagerExtensions.cs b/EstateReportingAPI.BusinessLogic/ReportingManagerExtensions.cs new file mode 100644 index 0000000..aba8d55 --- /dev/null +++ b/EstateReportingAPI.BusinessLogic/ReportingManagerExtensions.cs @@ -0,0 +1,197 @@ +using EstateReportingAPI.Models; +using TransactionProcessor.Database.Contexts; + +namespace EstateReportingAPI.BusinessLogic; + +public static class ReportingManagerExtensions{ + public static IQueryable ApplyMerchantFilter(this IQueryable query, + EstateManagementContext context, + List merchantIds) + { + if (merchantIds == null || merchantIds.Count == 0) + return query; + + return from q in query + join m in context.Merchants + on q.Txn.MerchantId equals m.MerchantId + where merchantIds.Contains(m.MerchantReportingId) + select q; + } + + public static IQueryable ApplyOperatorFilter(this IQueryable query, + EstateManagementContext context, + List operatorIds) + { + if (operatorIds == null || operatorIds.Count == 0) + return query; + + return from q in query + join o in context.Operators + on q.Txn.OperatorId equals o.OperatorId + where operatorIds.Contains(o.OperatorReportingId) + select q; + } + + public static IQueryable ApplyProductFilter(this IQueryable query, + EstateManagementContext context, + List productIds) + { + if (productIds == null || productIds.Count == 0) + return query; + + return from q in query + join cp in context.ContractProducts + on q.Txn.ContractProductId equals cp.ContractProductId + where productIds.Contains(cp.ContractProductReportingId) + select q; + } + + public static IQueryable ApplyProductGrouping(this IQueryable fees, + EstateManagementContext context) + { + return from f in fees + join cp in context.ContractProducts on f.Txn.ContractProductId equals cp.ContractProductId + join c in context.Contracts on cp.ContractId equals c.ContractId + join op in context.Operators on c.OperatorId equals op.OperatorId + group f by new { op.Name, cp.ProductName } into g + select new UnsettledFee + { + DimensionName = $"{g.Key.Name} - {g.Key.ProductName}", + FeesValue = g.Sum(x => x.Fee.CalculatedValue), + FeesCount = g.Count() + }; + } + + public static IQueryable ApplyMerchantGrouping(this IQueryable fees, + EstateManagementContext context) + { + return from f in fees + join merchant in context.Merchants on f.Fee.MerchantId equals merchant.MerchantId + group f by merchant.Name into g + select new UnsettledFee + { + DimensionName = g.Key, + FeesValue = g.Sum(x => x.Fee.CalculatedValue), + FeesCount = g.Count() + }; + } + + public static IQueryable ApplyOperatorGrouping(this IQueryable fees, + EstateManagementContext context) + { + return from f in fees + join op in context.Operators on f.Txn.OperatorId equals op.OperatorId + group f by op.Name into g + select new UnsettledFee + { + DimensionName = g.Key, + FeesValue = g.Sum(x => x.Fee.CalculatedValue), + FeesCount = g.Count() + }; + } + + public static IQueryable ApplyOperatorFilters(this IQueryable transactions, + List operators) { + + if (operators != null && operators.Any()) { + return transactions.Where(m => operators.Contains(m.Operator.OperatorReportingId)); + } + return transactions; + } + + public static IQueryable ApplyMerchantFilters(this IQueryable transactions, + List merchants) + { + + if (merchants != null && merchants.Any()) + { + return transactions.Where(m => merchants.Contains(m.Merchant.MerchantReportingId)); + } + return transactions; + } + + public static IQueryable ApplyValueRangeFilters(this IQueryable transactions, + ValueRange valueRange) + { + + if (valueRange != null) { + return transactions.Where(m => m.Transaction.TransactionAmount >= valueRange.StartValue && m.Transaction.TransactionAmount <= valueRange.EndValue); + } + + return transactions; + } + + public static IQueryable ApplyAuthCodeFilters(this IQueryable transactions, + String authCode) + { + + if (String.IsNullOrEmpty(authCode) == false) + { + transactions = transactions.Where(m => m.Transaction.AuthorisationCode == authCode); + } + + return transactions; + } + + public static IQueryable ApplyResponseCodeFilters(this IQueryable transactions, + String responseCode) + { + if (String.IsNullOrEmpty(responseCode) == false) { + return transactions.Where(m => m.Transaction.ResponseCode == responseCode); + } + + return transactions; + } + + public static IQueryable ApplyTransactionNumberFilters(this IQueryable transactions, + String transactionNumber) + { + if (String.IsNullOrEmpty(transactionNumber) == false) { + return transactions.Where(m => m.Transaction.TransactionNumber == transactionNumber); + } + + return transactions; + } + + + public static IQueryable ApplyFilters(this IQueryable transactions, + TransactionSearchRequest searchRequest) { + + transactions = transactions.ApplyOperatorFilters(searchRequest.Operators); + transactions = transactions.ApplyMerchantFilters(searchRequest.Merchants); + transactions = transactions.ApplyValueRangeFilters(searchRequest.ValueRange); + transactions = transactions.ApplyAuthCodeFilters(searchRequest.AuthCode); + transactions = transactions.ApplyResponseCodeFilters(searchRequest.ResponseCode); + transactions = transactions.ApplyTransactionNumberFilters(searchRequest.TransactionNumber); + + return transactions; + } + + public static IQueryable ApplyPagination(this IQueryable records, + PagingRequest pagingRequest) { + Int32 skipCount = 0; + if (pagingRequest.Page > 1) + { + skipCount = (pagingRequest.Page - 1) * pagingRequest.PageSize; + } + return records.Skip(skipCount).Take(pagingRequest.PageSize); + } + + public static IQueryable ApplySorting(this IQueryable records, + SortingRequest sortingRequest) { + if (sortingRequest == null) + return records; + + // Handle order by here, cant think of a better way of achieving this + return (sortingRequest.SortDirection, sortingRequest.SortField) switch + { + (SortDirection.Ascending, SortField.MerchantName) => records.OrderBy(m => m.Merchant.Name), + (SortDirection.Ascending, SortField.OperatorName) => records.OrderBy(m => m.Operator.Name), + (SortDirection.Ascending, SortField.TransactionAmount) => records.OrderBy(m => m.Transaction.TransactionAmount), + (SortDirection.Descending, SortField.MerchantName) => records.OrderByDescending(m => m.Merchant.Name), + (SortDirection.Descending, SortField.OperatorName) => records.OrderByDescending(m => m.Operator.Name), + (SortDirection.Descending, SortField.TransactionAmount) => records.OrderByDescending(m => m.Transaction.TransactionAmount), + _ => records + }; + } +} \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManagerRefactored.cs b/EstateReportingAPI.BusinessLogic/ReportingManagerRefactored.cs new file mode 100644 index 0000000..26f9678 --- /dev/null +++ b/EstateReportingAPI.BusinessLogic/ReportingManagerRefactored.cs @@ -0,0 +1,124 @@ +using EstateReportingAPI.Models; +using Microsoft.EntityFrameworkCore; +using Shared.EntityFramework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TransactionProcessor.Database.Contexts; +using TransactionProcessor.Database.Entities; +using Merchant = TransactionProcessor.Database.Entities.Merchant; +using Operator = TransactionProcessor.Database.Entities.Operator; + +namespace EstateReportingAPI.BusinessLogic +{ + public partial class ReportingManager : IReportingManager { + + private IQueryable BuildUnsettledFeesQuery(EstateManagementContext context, + DateTime startDate, + DateTime endDate) + { + return from merchantSettlementFee in context.MerchantSettlementFees + join transaction in context.Transactions + on merchantSettlementFee.TransactionId equals transaction.TransactionId + where merchantSettlementFee.FeeCalculatedDateTime.Date >= startDate && + merchantSettlementFee.FeeCalculatedDateTime.Date <= endDate + select new DatabaseProjections.FeeTransactionProjection { Fee = merchantSettlementFee, Txn = transaction }; + } + + private IQueryable BuildTransactionSearchQuery(EstateManagementContext context, + DateTime queryDate) { + return from txn in context.Transactions + join merchant in context.Merchants on txn.MerchantId equals merchant.MerchantId + join @operator in context.Operators on txn.OperatorId equals @operator.OperatorId + join product in context.ContractProducts on txn.ContractProductId equals product.ContractProductId + where txn.TransactionDate == queryDate + select new DatabaseProjections.TransactionSearchProjection { + Transaction = txn, + Merchant = merchant, + Operator = @operator, + Product = product + }; + } + + public async Task> GetUnsettledFees(Guid estateId, DateTime startDate, DateTime endDate, List merchantIds, List operatorIds, List productIds, GroupByOption? groupByOption, CancellationToken cancellationToken) + { + + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString()); + await using EstateManagementContext context = resolvedContext.Context; + + IQueryable query = BuildUnsettledFeesQuery(context, startDate, endDate) + .ApplyMerchantFilter(context, merchantIds) + .ApplyOperatorFilter(context, operatorIds) + .ApplyProductFilter(context, productIds); + + // Perform grouping + IQueryable groupedQuery = groupByOption switch + { + GroupByOption.Merchant => query.ApplyMerchantGrouping(context), + GroupByOption.Operator => query.ApplyOperatorGrouping(context), + GroupByOption.Product => query.ApplyProductGrouping(context), + }; + return await groupedQuery.ToListAsync(cancellationToken); + + } + + public async Task> TransactionSearch(Guid estateId, TransactionSearchRequest searchRequest, PagingRequest pagingRequest, SortingRequest sortingRequest, CancellationToken cancellationToken) + { + // Base query before any filtering is added + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, estateId.ToString()); + await using EstateManagementContext context = resolvedContext.Context; + + IQueryable mainQuery = BuildTransactionSearchQuery(context, searchRequest.QueryDate); + + mainQuery = mainQuery.ApplyFilters(searchRequest); + mainQuery = mainQuery.ApplySorting(sortingRequest); + mainQuery = mainQuery.ApplyPagination(pagingRequest); + + // Now build the results + List queryResults = await mainQuery.ToListAsync(cancellationToken); + + List results = new List(); + + queryResults.ForEach(qr => + { + results.Add(new TransactionResult + { + MerchantReportingId = qr.Merchant.MerchantReportingId, + ResponseCode = qr.Transaction.ResponseCode, + IsAuthorised = qr.Transaction.IsAuthorised, + MerchantName = qr.Merchant.Name, + OperatorName = qr.Operator.Name, + OperatorReportingId = qr.Operator.OperatorReportingId, + Product = qr.Product.ProductName, + ProductReportingId = qr.Product.ContractProductReportingId, + ResponseMessage = qr.Transaction.ResponseMessage, + TransactionDateTime = qr.Transaction.TransactionDateTime, + TransactionId = qr.Transaction.TransactionId, + TransactionReportingId = qr.Transaction.TransactionReportingId, + TransactionSource = qr.Transaction.TransactionSource.ToString(), + TransactionAmount = qr.Transaction.TransactionAmount + }); + }); + + return results; + } + + } + + public class DatabaseProjections { + public class FeeTransactionProjection + { + public MerchantSettlementFee Fee { get; set; } + public Transaction Txn { get; set; } + } + + public class TransactionSearchProjection { + public Transaction Transaction { get; set; } + public Operator Operator { get; set; } + public Merchant Merchant { get; set; } + public ContractProduct Product { get; set; } + } + } +}