From f312023ee88c3704b86b3025939b3815559ff121 Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Mon, 16 Feb 2026 20:21:29 +0000 Subject: [PATCH] Add Today's Settlement reporting endpoint and queries Introduced support for retrieving today's settlement summary and comparison data via a new API endpoint (`api/settlements/todayssettlements`). Added `SettlementQueries.TodaysSettlementQuery`, handler, and model. Enhanced `ReportingManager` to aggregate settled and pending settlement counts and values for both today and a comparison date. Registered the new handler in the mediator, mapped the endpoint in startup, and added comprehensive endpoint tests. Refactored LINQ queries for consistency and improved error handling. Existing endpoints remain unchanged. --- .../Queries/TransactionQueries.cs | 5 + .../ReportingManager.cs | 900 ++++++++---------- .../TransactionRequestHandler.cs | 12 + .../TransactionsEndpointTests.cs | 142 +++ .../{Archive => }/TodaysSettlement.cs | 0 .../TransactionSummaryByOperatorResponse.cs | 3 +- .../Bootstrapper/MediatorRegistry.cs | 3 + .../Endpoints/TransactionEndpoints.cs | 15 + .../Handlers/TransactionHandler.cs | 21 + EstateReportingAPI/Startup.cs | 1 + 10 files changed, 588 insertions(+), 514 deletions(-) rename EstateReportingAPI.Models/{Archive => }/TodaysSettlement.cs (100%) diff --git a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs index f2873b8..49f9358 100644 --- a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs +++ b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs @@ -14,4 +14,9 @@ public record TransactionSummaryByMerchantQuery(Guid EstateId, TransactionSummar public record TransactionSummaryByOperatorQuery(Guid EstateId, TransactionSummaryByOperatorRequest Request) : IRequest>; public record ProductPerformanceQuery(Guid EstateId, DateTime StartDate, DateTime EndDate) : IRequest>; public record TodaysSalesByHour(Guid estateId, DateTime comparisonDate) : IRequest>>; +} + +[ExcludeFromCodeCoverage] +public record SettlementQueries { + public record TodaysSettlementQuery(Guid EstateId, DateTime ComparisonDate) : IRequest>; } \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index d4c6445..2f6641a 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -54,12 +54,14 @@ Task> GetProductPerformanceReport(Transaction Task>> GetTodaysSalesByHour(TransactionQueries.TodaysSalesByHour request, CancellationToken cancellationToken); + Task> GetTodaysSettlement(SettlementQueries.TodaysSettlementQuery request, + CancellationToken cancellationToken); #endregion } public class ReportingManager : IReportingManager { private readonly IDbContextResolver Resolver; - + private Guid Id; private static readonly String EstateManagementDatabaseName = "TransactionProcessorReadModel"; @@ -73,38 +75,36 @@ public ReportingManager(IDbContextResolver resolver) { #region Methods - private static async Task> ExecuteQuerySafeSum(IQueryable query, CancellationToken cancellationToken, string contextMessage = null) - { - try - { + private static async Task> ExecuteQuerySafeSum(IQueryable query, + CancellationToken cancellationToken, + string contextMessage = null) { + try { T item = await query.SumAsync(cancellationToken); return Result.Success(item); } - catch (Exception ex) - { + catch (Exception ex) { string msg = contextMessage == null ? $"Error executing query: {ex.Message}" : $"{contextMessage}: {ex.Message}"; return Result.Failure(msg); } } - private static async Task>> ExecuteQuerySafeToList(IQueryable query,CancellationToken cancellationToken,string contextMessage = null) - { - try - { + private static async Task>> ExecuteQuerySafeToList(IQueryable query, + CancellationToken cancellationToken, + string contextMessage = null) { + try { List items = await query.ToListAsync(cancellationToken); return Result.Success(items); } - catch (Exception ex) - { + catch (Exception ex) { string msg = contextMessage == null ? $"Error executing query: {ex.Message}" : $"{contextMessage}: {ex.Message}"; return Result.Failure(msg); } } - private static async Task> ExecuteQuerySafeSingleOrDefault(IQueryable query, CancellationToken cancellationToken, string contextMessage = null) - { - try - { + private static async Task> ExecuteQuerySafeSingleOrDefault(IQueryable query, + CancellationToken cancellationToken, + string contextMessage = null) { + try { T item = await query.SingleOrDefaultAsync(cancellationToken); if (item == null) @@ -112,32 +112,29 @@ private static async Task> ExecuteQuerySafeSingleOrDefault(IQueryab return Result.Success(item); } - catch (Exception ex) - { + catch (Exception ex) { string msg = contextMessage == null ? $"Error executing query: {ex.Message}" : $"{contextMessage}: {ex.Message}"; return Result.Failure(msg); } } public async Task>> GetCalendarComparisonDates(CalendarQueries.GetComparisonDatesQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - + DateTime today = DateTime.Today; DateTime startDate = today.AddYears(-1); DateTime endDate = today.AddDays(-1); // yesterday - var result = await ExecuteQuerySafeToList( - context.Calendar.Where(c => c.Date >= startDate && c.Date < endDate) - .OrderByDescending(c => c.Date), cancellationToken, "Error retrieving calendar comparison dates"); + var result = await ExecuteQuerySafeToList(context.Calendar.Where(c => c.Date >= startDate && c.Date < endDate).OrderByDescending(c => c.Date), cancellationToken, "Error retrieving calendar comparison dates"); if (result.IsFailed) return ResultHelpers.CreateFailure(result); var entities = result.Data; - if (entities.Any() == false) + if (entities.Any() == false) return Result.NotFound("No calendar dates found"); List response = new(); @@ -165,8 +162,7 @@ public async Task>> GetCalendarDates(CalendarQueries.GetAl await using EstateManagementContext context = resolvedContext.Context; - var result = await ExecuteQuerySafeToList( - context.Calendar.Where(c => c.Date <= DateTime.Now.Date), cancellationToken, "Error retrieving calendar dates"); + var result = await ExecuteQuerySafeToList(context.Calendar.Where(c => c.Date <= DateTime.Now.Date), cancellationToken, "Error retrieving calendar dates"); if (result.IsFailed) return ResultHelpers.CreateFailure(result); @@ -193,25 +189,23 @@ public async Task>> GetCalendarDates(CalendarQueries.GetAl } public async Task>> GetCalendarYears(CalendarQueries.GetYearsQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - var result = await ExecuteQuerySafeToList( - context.Calendar.Where(c => c.Date <= DateTime.Now.Date).GroupBy(c => c.Year).Select(y => y.Key), - cancellationToken, "Error retrieving calendar years"); + var result = await ExecuteQuerySafeToList(context.Calendar.Where(c => c.Date <= DateTime.Now.Date).GroupBy(c => c.Year).Select(y => y.Key), cancellationToken, "Error retrieving calendar years"); if (result.IsFailed) return ResultHelpers.CreateFailure(result); - + return result; } public async Task>> GetContracts(ContractQueries.GetContractsQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - + // Step 1: load contracts with operator name via a left-join (translatable) var baseContractsQuery = (from c in context.Contracts join o in context.Operators on c.OperatorId equals o.OperatorId into ops @@ -238,17 +232,14 @@ from o in ops.DefaultIfEmpty() var contractIds = baseContractEntities.Select(b => b.ContractId).ToList(); // Step 2: load related products for all contracts in one query - var productsQuery = context.ContractProducts - .Where(cp => contractIds.Contains(cp.ContractId)) - .Select(cp => new - { - cp.ContractProductId, - cp.ContractId, - cp.DisplayText, - cp.ProductName, - cp.ProductType, - cp.Value - }); + var productsQuery = context.ContractProducts.Where(cp => contractIds.Contains(cp.ContractId)).Select(cp => new { + cp.ContractProductId, + cp.ContractId, + cp.DisplayText, + cp.ProductName, + cp.ProductType, + cp.Value + }); var productsQueryResult = await ExecuteQuerySafeToList(productsQuery, cancellationToken, "Error retrieving contracts - Step 2"); @@ -260,18 +251,15 @@ from o in ops.DefaultIfEmpty() var productIds = productEntities.Select(p => p.ContractProductId).ToList(); // Step 3: load fees for those products in one query - var feesQuery = context.ContractProductTransactionFees - .Where(tf => productIds.Contains(tf.ContractProductId)) - .Select(tf => new - { - tf.CalculationType, - tf.ContractProductTransactionFeeId, - tf.FeeType, - tf.Value, - tf.ContractProductId, - tf.Description, - tf.IsEnabled - }); + var feesQuery = context.ContractProductTransactionFees.Where(tf => productIds.Contains(tf.ContractProductId)).Select(tf => new { + tf.CalculationType, + tf.ContractProductTransactionFeeId, + tf.FeeType, + tf.Value, + tf.ContractProductId, + tf.Description, + tf.IsEnabled + }); var feesQueryResult = await ExecuteQuerySafeToList(feesQuery, cancellationToken, "Error retrieving contracts - Step 3"); @@ -281,77 +269,69 @@ from o in ops.DefaultIfEmpty() var feesEntities = feesQueryResult.Data; // Assemble the model in memory - List result = baseContractEntities.Select(b => new Contract - { + List result = baseContractEntities.Select(b => new Contract { ContractId = b.ContractId, ContractReportingId = b.ContractReportingId, Description = b.Description, EstateId = b.EstateId, OperatorName = b.OperatorName, OperatorId = b.OperatorId, - Products = productEntities - .Where(p => p.ContractId == b.ContractId) - .Select(p => new Models.ContractProduct - { - ContractId = p.ContractId, - ProductId = p.ContractProductId, - DisplayText = p.DisplayText, - ProductName = p.ProductName, - ProductType = p.ProductType, - Value = p.Value, - TransactionFees = feesEntities - .Where(f => f.ContractProductId == p.ContractProductId && f.IsEnabled) - .Select(f => new ContractProductTransactionFee { Description = f.Description,Value = f.Value, CalculationType = f.CalculationType, FeeType = f.FeeType, TransactionFeeId = f.ContractProductTransactionFeeId}) - .ToList() - }) - .ToList() + Products = productEntities.Where(p => p.ContractId == b.ContractId).Select(p => new Models.ContractProduct { + ContractId = p.ContractId, + ProductId = p.ContractProductId, + DisplayText = p.DisplayText, + ProductName = p.ProductName, + ProductType = p.ProductType, + Value = p.Value, + TransactionFees = feesEntities.Where(f => f.ContractProductId == p.ContractProductId && f.IsEnabled).Select(f => new ContractProductTransactionFee { + Description = f.Description, + Value = f.Value, + CalculationType = f.CalculationType, + FeeType = f.FeeType, + TransactionFeeId = f.ContractProductTransactionFeeId + }).ToList() + }).ToList() }).ToList(); return Result.Success(result); } public async Task> GetContract(ContractQueries.GetContractQuery request, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; // Step 1: load contracts with operator name via a left-join (translatable) var baseContractQuery = (from c in context.Contracts - join o in context.Operators on c.OperatorId equals o.OperatorId into ops - from o in ops.DefaultIfEmpty() - where c.ContractId == request.ContractId - select new - { - c.ContractId, - c.ContractReportingId, - c.Description, - c.EstateId, - c.OperatorId, - OperatorName = o != null ? o.Name : null - }) - .OrderByDescending(x => x.Description); + join o in context.Operators on c.OperatorId equals o.OperatorId into ops + from o in ops.DefaultIfEmpty() + where c.ContractId == request.ContractId + select new { + c.ContractId, + c.ContractReportingId, + c.Description, + c.EstateId, + c.OperatorId, + OperatorName = o != null ? o.Name : null + }).OrderByDescending(x => x.Description); var baseContractQueryResult = await ExecuteQuerySafeSingleOrDefault(baseContractQuery, cancellationToken, "Error retrieving contract - Step 1"); if (baseContractQueryResult.IsFailed) return ResultHelpers.CreateFailure(baseContractQueryResult); - + var baseContract = baseContractQueryResult.Data; - + // Step 2: load related products for all contracts in one query - var productsQuery = context.ContractProducts - .Where(cp => cp.ContractId == baseContract.ContractId) - .Select(cp => new - { - cp.ContractProductId, - cp.ContractId, - cp.DisplayText, - cp.ProductName, - cp.ProductType, - cp.Value - }); + var productsQuery = context.ContractProducts.Where(cp => cp.ContractId == baseContract.ContractId).Select(cp => new { + cp.ContractProductId, + cp.ContractId, + cp.DisplayText, + cp.ProductName, + cp.ProductType, + cp.Value + }); var productsQueryResult = await ExecuteQuerySafeToList(productsQuery, cancellationToken, "Error retrieving contract - Step 2"); @@ -362,17 +342,14 @@ from o in ops.DefaultIfEmpty() var productIds = products.Select(p => p.ContractProductId).ToList(); // Step 3: load fees for those products in one query - var feesQuery = context.ContractProductTransactionFees - .Where(tf => productIds.Contains(tf.ContractProductId)) - .Select(tf => new - { - tf.CalculationType, - tf.ContractProductTransactionFeeId, - tf.FeeType, - tf.Value, - tf.ContractProductId, - tf.Description - }); + var feesQuery = context.ContractProductTransactionFees.Where(tf => productIds.Contains(tf.ContractProductId)).Select(tf => new { + tf.CalculationType, + tf.ContractProductTransactionFeeId, + tf.FeeType, + tf.Value, + tf.ContractProductId, + tf.Description + }); var feesQueryResult = await ExecuteQuerySafeToList(feesQuery, cancellationToken, "Error retrieving contract - Step 3"); @@ -382,41 +359,39 @@ from o in ops.DefaultIfEmpty() var fees = feesQueryResult.Data; // Assemble the model in memory - Contract result = new Contract - { + Contract result = new Contract { ContractId = baseContract.ContractId, ContractReportingId = baseContract.ContractReportingId, Description = baseContract.Description, EstateId = baseContract.EstateId, OperatorName = baseContract.OperatorName, OperatorId = baseContract.OperatorId, - Products = products - .Where(p => p.ContractId == baseContract.ContractId) - .Select(p => new Models.ContractProduct - { - ContractId = p.ContractId, - ProductId = p.ContractProductId, - DisplayText = p.DisplayText, - ProductName = p.ProductName, - ProductType = p.ProductType, - Value = p.Value, - TransactionFees = fees - .Where(f => f.ContractProductId == p.ContractProductId) - .Select(f => new ContractProductTransactionFee { Description = f.Description, Value = f.Value, CalculationType = f.CalculationType, FeeType = f.FeeType, TransactionFeeId = f.ContractProductTransactionFeeId }) - .ToList() - }) - .ToList() + Products = products.Where(p => p.ContractId == baseContract.ContractId).Select(p => new Models.ContractProduct { + ContractId = p.ContractId, + ProductId = p.ContractProductId, + DisplayText = p.DisplayText, + ProductName = p.ProductName, + ProductType = p.ProductType, + Value = p.Value, + TransactionFees = fees.Where(f => f.ContractProductId == p.ContractProductId).Select(f => new ContractProductTransactionFee { + Description = f.Description, + Value = f.Value, + CalculationType = f.CalculationType, + FeeType = f.FeeType, + TransactionFeeId = f.ContractProductTransactionFeeId + }).ToList() + }).ToList() }; return Result.Success(result); } public async Task>> GetRecentContracts(ContractQueries.GetRecentContractsQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - IQueryable contractsQuery = context.Contracts.Select(c => new Contract(){ + IQueryable contractsQuery = context.Contracts.Select(c => new Contract() { ContractId = c.ContractId, ContractReportingId = c.ContractReportingId, Description = c.Description, @@ -432,17 +407,15 @@ public async Task>> GetRecentContracts(ContractQueries.Get return result; } - - public async Task> GetTodaysFailedSales(TransactionQueries.TodaysFailedSales request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; var todaysSalesQuery = (from t in context.TodayTransactions where t.IsAuthorised == false && t.TransactionType == "Sale" && t.ResponseCode == request.ResponseCode select t.TransactionAmount); var comparisonSalesQuery = (from t in context.TransactionHistory where t.IsAuthorised == false && t.TransactionType == "Sale" && t.TransactionDate == request.ComparisonDate && t.TransactionTime <= DateTime.Now.TimeOfDay && t.ResponseCode == request.ResponseCode select t.TransactionAmount); - + var todaysSalesQueryResult = await ExecuteQuerySafeToList(todaysSalesQuery, cancellationToken, "Error retrieving todays failed sales"); if (todaysSalesQueryResult.IsFailed) return ResultHelpers.CreateFailure(todaysSalesQueryResult); @@ -464,7 +437,7 @@ public async Task> GetTodaysFailedSales(TransactionQueries.T } public async Task> GetTodaysSales(TransactionQueries.TodaysSalesQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; @@ -493,29 +466,20 @@ public async Task> GetTodaysSales(TransactionQueries.TodaysS TodaysSalesCount = todaysSalesCount, TodaysSalesValue = todaysSalesValue, TodaysAverageSalesValue = SafeDivide(todaysSalesValue, todaysSalesCount), - ComparisonAverageSalesValue = SafeDivide(comparisonSalesValue,comparisonSalesCount) + ComparisonAverageSalesValue = SafeDivide(comparisonSalesValue, comparisonSalesCount) }; return Result.Success(response); } public async Task>> GetEstateOperators(EstateQueries.GetEstateOperatorsQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - var operatorQuery = context.EstateOperators - .Join( - context.Operators, - eo => new { eo.OperatorId, eo.EstateId }, - op => new { op.OperatorId, op.EstateId }, - (eo, op) => new - { - EstateOperator = eo, - Operator = op - }) - .Where(e => e.EstateOperator.EstateId == request.EstateId && (e.EstateOperator.IsDeleted ?? false) == false); + var operatorQuery = context.EstateOperators.Join(context.Operators, eo => new { eo.OperatorId, eo.EstateId }, op => new { op.OperatorId, op.EstateId }, (eo, + op) => new { EstateOperator = eo, Operator = op }).Where(e => e.EstateOperator.EstateId == request.EstateId && (e.EstateOperator.IsDeleted ?? false) == false); var operatorQueryResult = await ExecuteQuerySafeToList(operatorQuery, cancellationToken, "Error retrieving estate operators"); if (operatorQueryResult.IsFailed) @@ -525,10 +489,7 @@ public async Task>> GetEstateOperators(EstateQueries List operators = new(); foreach (var operatorEntity in operatorEntities) { - operators.Add(new EstateOperator() { - Name = operatorEntity.Operator.Name, - OperatorId = operatorEntity.EstateOperator.OperatorId - }); + operators.Add(new EstateOperator() { Name = operatorEntity.Operator.Name, OperatorId = operatorEntity.EstateOperator.OperatorId }); } return Result.Success(operators); @@ -569,33 +530,14 @@ public async Task> GetEstate(EstateQueries.GetEstateQuery request return ResultHelpers.CreateFailure(contractsListQueryResult); var contracts = contractsListQueryResult.Data; - Estate result = new() - { + Estate result = new() { EstateId = estate.EstateId, EstateName = estate.Name, Reference = estate.Reference, - Operators = operators.Select(o => new Models.EstateOperator - { - OperatorId = o.OperatorId, - Name = o.Name, - }).ToList(), - Users = users.Select(u => new Models.EstateUser - { - UserId = u.SecurityUserId, - EmailAddress= u.EmailAddress, - CreatedDateTime = u.CreatedDateTime - }).ToList(), - Merchants = merchants.Select(m => new Models.EstateMerchant - { - MerchantId = m.MerchantId, - Name = m.Name, - Reference = m.Reference - }).ToList(), - Contracts = contracts.Select(c => new Models.EstateContract - { - ContractId = c.ContractId, - Name = c.Description, - }).ToList() + Operators = operators.Select(o => new Models.EstateOperator { OperatorId = o.OperatorId, Name = o.Name, }).ToList(), + Users = users.Select(u => new Models.EstateUser { UserId = u.SecurityUserId, EmailAddress = u.EmailAddress, CreatedDateTime = u.CreatedDateTime }).ToList(), + Merchants = merchants.Select(m => new Models.EstateMerchant { MerchantId = m.MerchantId, Name = m.Name, Reference = m.Reference }).ToList(), + Contracts = contracts.Select(c => new Models.EstateContract { ContractId = c.ContractId, Name = c.Description, }).ToList() }; return result; @@ -615,12 +557,9 @@ private Decimal SafeDivide(Decimal number, return number / divisor; } - - public async Task>> GetRecentMerchants(MerchantQueries.GetRecentMerchantsQuery request, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; @@ -634,7 +573,7 @@ public async Task>> GetRecentMerchants(MerchantQueries.Get MerchantId = m.MerchantId, Reference = m.Reference, AddressInfo = context.MerchantAddresses.Where(ma => ma.MerchantId == m.MerchantId).OrderByDescending(ma => ma.CreatedDateTime).Select(ma => new { - ma.AddressLine1, + ma.AddressLine1, ma.AddressLine2, ma.Country, ma.PostalCode, @@ -642,11 +581,7 @@ public async Task>> GetRecentMerchants(MerchantQueries.Get ma.Town // Add more properties as needed }).FirstOrDefault(), // Get the first matching MerchantAddress or null - ContactInfo = context.MerchantContacts.Where(mc => mc.MerchantId == m.MerchantId).OrderByDescending(mc => mc.CreatedDateTime).Select(mc => new { - mc.Name, - mc.EmailAddress, - mc.PhoneNumber - }).FirstOrDefault(), // Get the first matching MerchantContact or null + ContactInfo = context.MerchantContacts.Where(mc => mc.MerchantId == m.MerchantId).OrderByDescending(mc => mc.CreatedDateTime).Select(mc => new { mc.Name, mc.EmailAddress, mc.PhoneNumber }).FirstOrDefault(), // Get the first matching MerchantContact or null EstateReportingId = context.Estates.Single(e => e.EstateId == m.EstateId).EstateReportingId }).OrderByDescending(m => m.CreatedDateTime).Take(3); @@ -657,10 +592,8 @@ public async Task>> GetRecentMerchants(MerchantQueries.Get var recentMerchants = recentMerchantsResult.Data; List merchantList = new(); - foreach (var merchant in recentMerchants) - { - Merchant model = new() - { + foreach (var merchant in recentMerchants) { + Merchant model = new() { MerchantId = merchant.MerchantId, Name = merchant.Name, Reference = merchant.Reference, @@ -690,13 +623,12 @@ public async Task>> GetRecentMerchants(MerchantQueries.Get } public async Task> GetMerchantsTransactionKpis(MerchantQueries.GetTransactionKpisQuery request, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - + var merchantsQuery = context.Merchants.Select(m => new { m.Name, m.LastSaleDate, m.LastSaleDateTime }); - var merchantsQueryResult = await ExecuteQuerySafeToList(merchantsQuery, cancellationToken, "Error retrieving merchants for KPI's"); + var merchantsQueryResult = await ExecuteQuerySafeToList(merchantsQuery, cancellationToken, "Error retrieving merchants for KPI's"); if (merchantsQueryResult.IsFailed) return ResultHelpers.CreateFailure(merchantsQueryResult); @@ -714,31 +646,28 @@ public async Task> GetMerchantsTransactionKpis(MerchantQueri } public async Task>> GetOperators(OperatorQueries.GetOperatorsQuery request, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; var operatorQuery = (from o in context.Operators - select new - { - Name = o.Name, - EstateReportingId = context.Estates.Single(e => e.EstateId == o.EstateId).EstateReportingId, - OperatorId = o.OperatorId, - OperatorReportingId = o.OperatorReportingId, - RequireCustomMerchantNumber = o.RequireCustomMerchantNumber, - RequireCustomTerminalNumber = o.RequireCustomTerminalNumber - }); + select new { + Name = o.Name, + EstateReportingId = context.Estates.Single(e => e.EstateId == o.EstateId).EstateReportingId, + OperatorId = o.OperatorId, + OperatorReportingId = o.OperatorReportingId, + RequireCustomMerchantNumber = o.RequireCustomMerchantNumber, + RequireCustomTerminalNumber = o.RequireCustomTerminalNumber + }); var operatorResult = await ExecuteQuerySafeToList(operatorQuery, cancellationToken, "Error retrieving operator"); if (operatorResult.IsFailed) return ResultHelpers.CreateFailure(operatorResult); - + List operators = new List(); foreach (var op in operatorResult.Data) { - operators.Add(new Operator - { + operators.Add(new Operator { Name = op.Name, EstateReportingId = op.EstateReportingId, OperatorId = op.OperatorId, @@ -752,29 +681,27 @@ public async Task>> GetOperators(OperatorQueries.GetOperat } public async Task> GetOperator(OperatorQueries.GetOperatorQuery request, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; var operatorQuery = (from o in context.Operators - where o.OperatorId == request.OperatorId - select new { - Name = o.Name, - EstateReportingId = context.Estates.Single(e => e.EstateId == o.EstateId).EstateReportingId, - OperatorId = o.OperatorId, - OperatorReportingId = o.OperatorReportingId, - RequireCustomMerchantNumber = o.RequireCustomMerchantNumber, - RequireCustomTerminalNumber = o.RequireCustomTerminalNumber - }); + where o.OperatorId == request.OperatorId + select new { + Name = o.Name, + EstateReportingId = context.Estates.Single(e => e.EstateId == o.EstateId).EstateReportingId, + OperatorId = o.OperatorId, + OperatorReportingId = o.OperatorReportingId, + RequireCustomMerchantNumber = o.RequireCustomMerchantNumber, + RequireCustomTerminalNumber = o.RequireCustomTerminalNumber + }); var operatorResult = await ExecuteQuerySafeSingleOrDefault(operatorQuery, cancellationToken, "Error retrieving operator"); if (operatorResult.IsFailed) return ResultHelpers.CreateFailure(operatorResult); - var @operator = new Operator - { + var @operator = new Operator { Name = operatorResult.Data.Name, EstateReportingId = operatorResult.Data.EstateReportingId, OperatorId = operatorResult.Data.OperatorId, @@ -787,15 +714,14 @@ public async Task> GetOperator(OperatorQueries.GetOperatorQuery } public async Task> GetTransactionDetailReport(TransactionQueries.TransactionDetailReportQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { TransactionDetailReportResponse response = null; using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; var query = from t in context.Transactions - join cp in context.ContractProducts - on new { t.ContractProductId, t.ContractId } equals new { cp.ContractProductId, cp.ContractId } + join cp in context.ContractProducts on new { t.ContractProductId, t.ContractId } equals new { cp.ContractProductId, cp.ContractId } join m in context.Merchants on t.MerchantId equals m.MerchantId join o in context.Operators on t.OperatorId equals o.OperatorId join msf in context.MerchantSettlementFees on t.TransactionId equals msf.TransactionId into msfJoin @@ -803,11 +729,8 @@ from msf in msfJoin.DefaultIfEmpty() // left join Settlements (msf may be null) join s in context.Settlements on msf.SettlementId equals s.SettlementId into sJoin from s in sJoin.DefaultIfEmpty() - where t.TransactionType != "Logon" - && t.TransactionDate >= request.Request.StartDate - && t.TransactionDate <= request.Request.EndDate - select new - { + where t.TransactionType != "Logon" && t.TransactionDate >= request.Request.StartDate && t.TransactionDate <= request.Request.EndDate + select new { t.TransactionId, t.TransactionDateTime, MerchantId = m.MerchantId, @@ -830,12 +753,12 @@ from s in sJoin.DefaultIfEmpty() if (request.Request.Merchants != null && request.Request.Merchants.Any()) { query = query.Where(q => request.Request.Merchants.Contains(q.MerchantReportingId)); } - if (request.Request.Products != null && request.Request.Products.Any()) - { + + if (request.Request.Products != null && request.Request.Products.Any()) { query = query.Where(q => request.Request.Products.Contains(q.ContractProductReportingId)); } - if (request.Request.Operators != null && request.Request.Operators.Any()) - { + + if (request.Request.Operators != null && request.Request.Operators.Any()) { query = query.Where(q => request.Request.Operators.Contains(q.OperatorReportingId)); } @@ -848,12 +771,8 @@ from s in sJoin.DefaultIfEmpty() var queryResults = queryResult.Data; if (queryResults.Any() == false) - return new TransactionDetailReportResponse - { - Summary = new TransactionDetailSummary(), - Transactions = new List() - }; - + return new TransactionDetailReportResponse { Summary = new TransactionDetailSummary(), Transactions = new List() }; + // Now to translate the results response = new TransactionDetailReportResponse { Transactions = queryResults.Select(q => new TransactionDetail { @@ -874,11 +793,7 @@ from s in sJoin.DefaultIfEmpty() TotalFees = q.FeeValue, SettlementReference = q.SettlementId.ToString() }).ToList(), - Summary = new TransactionDetailSummary { - TransactionCount = queryResults.Count(), - TotalValue = queryResults.Sum(q => q.Value), - TotalFees = queryResults.Sum(q => q.FeeValue) - } + Summary = new TransactionDetailSummary { TransactionCount = queryResults.Count(), TotalValue = queryResults.Sum(q => q.Value), TotalFees = queryResults.Sum(q => q.FeeValue) } }; return Result.Success(response); @@ -891,15 +806,11 @@ public async Task> GetTransactionSu using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - var query = - from t in context.Transactions + var query = from t in context.Transactions join m in context.Merchants on t.MerchantId equals m.MerchantId join o in context.Operators on t.OperatorId equals o.OperatorId - where t.TransactionType == "Sale" - && t.TransactionDate >= request.Request.StartDate - && t.TransactionDate <= request.Request.EndDate - group t by new - { + where t.TransactionType == "Sale" && t.TransactionDate >= request.Request.StartDate && t.TransactionDate <= request.Request.EndDate + group t by new { t.MerchantId, m.MerchantReportingId, MerchantName = m.Name, @@ -907,8 +818,7 @@ join o in context.Operators on t.OperatorId equals o.OperatorId o.OperatorReportingId } into g - select new - { + select new { g.Key.MerchantId, g.Key.MerchantReportingId, g.Key.MerchantName, @@ -921,35 +831,28 @@ into g }; // Now apply the filters - if (request.Request.Merchants != null && request.Request.Merchants.Any()) - { + if (request.Request.Merchants != null && request.Request.Merchants.Any()) { query = query.Where(q => request.Request.Merchants.Contains(q.MerchantReportingId)); } - if (request.Request.Operators != null && request.Request.Operators.Any()) - { + + if (request.Request.Operators != null && request.Request.Operators.Any()) { query = query.Where(q => request.Request.Operators.Contains(q.OperatorReportingId)); } var finalQuery = from x in query - group x by new - { - x.MerchantId, - x.MerchantReportingId, - MerchantName = x.MerchantName, - } - into g - select new - { - g.Key.MerchantId, - g.Key.MerchantReportingId, - g.Key.MerchantName, - TotalCount = g.Sum(x => x.TotalCount), - TotalValue = g.Sum(x => x.TotalValue), - AverageValue = g.Count() > 0 ? g.Sum(x => x.TotalValue) / g.Count() : 0m, - AuthorisedCount = g.Sum(x => x.AuthorisedCount), - DeclinedCount = g.Sum(x => x.DeclinedCount), - AuthorisedPercentage = g.Sum(x => x.TotalCount) > 0 ? (decimal)g.Sum(x => x.AuthorisedCount) / (decimal)g.Sum(x => x.TotalCount) : 0m - }; + group x by new { x.MerchantId, x.MerchantReportingId, MerchantName = x.MerchantName, } + into g + select new { + g.Key.MerchantId, + g.Key.MerchantReportingId, + g.Key.MerchantName, + TotalCount = g.Sum(x => x.TotalCount), + TotalValue = g.Sum(x => x.TotalValue), + AverageValue = g.Count() > 0 ? g.Sum(x => x.TotalValue) / g.Count() : 0m, + AuthorisedCount = g.Sum(x => x.AuthorisedCount), + DeclinedCount = g.Sum(x => x.DeclinedCount), + AuthorisedPercentage = g.Sum(x => x.TotalCount) > 0 ? (decimal)g.Sum(x => x.AuthorisedCount) / (decimal)g.Sum(x => x.TotalCount) : 0m + }; var queryResult = await ExecuteQuerySafeToList(finalQuery, cancellationToken, "Error retrieving transaction summary by merchant report"); @@ -960,17 +863,11 @@ into g var queryResults = queryResult.Data; if (queryResults.Any() == false) - return new TransactionSummaryByMerchantResponse - { - Summary = new MerchantDetailSummary(), - Merchants= new List() - }; + return new TransactionSummaryByMerchantResponse { Summary = new MerchantDetailSummary(), Merchants = new List() }; // Now to translate the results - response = new TransactionSummaryByMerchantResponse - { - Merchants = queryResults.Select(q => new MerchantDetail - { + response = new TransactionSummaryByMerchantResponse { + Merchants = queryResults.Select(q => new MerchantDetail { MerchantId = q.MerchantId, MerchantReportingId = q.MerchantReportingId, MerchantName = q.MerchantName, @@ -981,14 +878,7 @@ into g TotalCount = q.TotalCount, TotalValue = q.TotalValue }).ToList(), - Summary = new MerchantDetailSummary - { - TotalCount = queryResults.Sum(q => q.TotalCount), - TotalValue = queryResults.Sum(q => q.TotalValue), - AverageValue = this.SafeDivide(queryResults.Sum(q => q.TotalValue) - ,queryResults.Sum(q => q.TotalCount)), - TotalMerchants = queryResults.Count() - } + Summary = new MerchantDetailSummary { TotalCount = queryResults.Sum(q => q.TotalCount), TotalValue = queryResults.Sum(q => q.TotalValue), AverageValue = this.SafeDivide(queryResults.Sum(q => q.TotalValue), queryResults.Sum(q => q.TotalCount)), TotalMerchants = queryResults.Count() } }; return response; @@ -1000,15 +890,11 @@ public async Task> GetTransactionSu using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - var query = - from t in context.Transactions + var query = from t in context.Transactions join m in context.Merchants on t.MerchantId equals m.MerchantId join o in context.Operators on t.OperatorId equals o.OperatorId - where t.TransactionType == "Sale" - && t.TransactionDate >= request.Request.StartDate - && t.TransactionDate <= request.Request.EndDate - group t by new - { + where t.TransactionType == "Sale" && t.TransactionDate >= request.Request.StartDate && t.TransactionDate <= request.Request.EndDate + group t by new { t.MerchantId, m.MerchantReportingId, MerchantName = m.Name, @@ -1017,8 +903,7 @@ join o in context.Operators on t.OperatorId equals o.OperatorId OperatorName = o.Name } into g - select new - { + select new { g.Key.MerchantId, g.Key.MerchantReportingId, g.Key.MerchantName, @@ -1032,35 +917,28 @@ into g }; // Now apply the filters - if (request.Request.Merchants != null && request.Request.Merchants.Any()) - { + if (request.Request.Merchants != null && request.Request.Merchants.Any()) { query = query.Where(q => request.Request.Merchants.Contains(q.MerchantReportingId)); } - if (request.Request.Operators != null && request.Request.Operators.Any()) - { + + if (request.Request.Operators != null && request.Request.Operators.Any()) { query = query.Where(q => request.Request.Operators.Contains(q.OperatorReportingId)); } var finalQuery = from x in query - group x by new - { - x.OperatorId, - x.OperatorReportingId, - OperatorName = x.OperatorName, - } - into g - select new - { - g.Key.OperatorId, - g.Key.OperatorReportingId, - g.Key.OperatorName, - TotalCount = g.Sum(x => x.TotalCount), - TotalValue = g.Sum(x => x.TotalValue), - AverageValue = g.Count() > 0 ? g.Sum(x => x.TotalValue) / g.Count() : 0m, - AuthorisedCount = g.Sum(x => x.AuthorisedCount), - DeclinedCount = g.Sum(x => x.DeclinedCount), - AuthorisedPercentage = g.Sum(x => x.TotalCount) > 0 ? (decimal)g.Sum(x => x.AuthorisedCount) / (decimal)g.Sum(x => x.TotalCount) : 0m - }; + group x by new { x.OperatorId, x.OperatorReportingId, OperatorName = x.OperatorName, } + into g + select new { + g.Key.OperatorId, + g.Key.OperatorReportingId, + g.Key.OperatorName, + TotalCount = g.Sum(x => x.TotalCount), + TotalValue = g.Sum(x => x.TotalValue), + AverageValue = g.Count() > 0 ? g.Sum(x => x.TotalValue) / g.Count() : 0m, + AuthorisedCount = g.Sum(x => x.AuthorisedCount), + DeclinedCount = g.Sum(x => x.DeclinedCount), + AuthorisedPercentage = g.Sum(x => x.TotalCount) > 0 ? (decimal)g.Sum(x => x.AuthorisedCount) / (decimal)g.Sum(x => x.TotalCount) : 0m + }; var queryResult = await ExecuteQuerySafeToList(finalQuery, cancellationToken, "Error retrieving transaction summary by operator report"); @@ -1071,17 +949,11 @@ into g var queryResults = queryResult.Data; if (queryResults.Any() == false) - return new TransactionSummaryByOperatorResponse - { - Summary = new OperatorDetailSummary(), - Operators = new List() - }; + return new TransactionSummaryByOperatorResponse { Summary = new OperatorDetailSummary(), Operators = new List() }; // Now to translate the results - response = new TransactionSummaryByOperatorResponse - { - Operators = queryResults.Select(q => new OperatorDetail - { + response = new TransactionSummaryByOperatorResponse { + Operators = queryResults.Select(q => new OperatorDetail { OperatorId = q.OperatorId, OperatorReportingId = q.OperatorReportingId, OperatorName = q.OperatorName, @@ -1092,14 +964,7 @@ into g TotalCount = q.TotalCount, TotalValue = q.TotalValue }).ToList(), - Summary = new OperatorDetailSummary - { - TotalCount = queryResults.Sum(q => q.TotalCount), - TotalValue = queryResults.Sum(q => q.TotalValue), - AverageValue = this.SafeDivide(queryResults.Sum(q => q.TotalValue) - , queryResults.Sum(q => q.TotalCount)), - TotalOperators = queryResults.Count() - } + Summary = new OperatorDetailSummary { TotalCount = queryResults.Sum(q => q.TotalCount), TotalValue = queryResults.Sum(q => q.TotalValue), AverageValue = this.SafeDivide(queryResults.Sum(q => q.TotalValue), queryResults.Sum(q => q.TotalCount)), TotalOperators = queryResults.Count() } }; return response; @@ -1109,43 +974,29 @@ public async Task>> GetMerchants(MerchantQueries.GetMercha CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - - var merchantWithAddresses = context.Merchants - .Where(m => m.EstateId == request.EstateId) - .GroupJoin( - context.MerchantAddresses, - m => m.MerchantId, - a => a.MerchantId, - (m, addresses) => new - { - Merchant = m, - Address = addresses.First() - }) - .AsQueryable(); + + var merchantWithAddresses = context.Merchants.Where(m => m.EstateId == request.EstateId).GroupJoin(context.MerchantAddresses, m => m.MerchantId, a => a.MerchantId, (m, + addresses) => new { Merchant = m, Address = addresses.First() }).AsQueryable(); // Now apply the other filters from request.QueryOptions if (String.IsNullOrEmpty(request.QueryOptions.Name) == false) { merchantWithAddresses = merchantWithAddresses.Where(m => m.Merchant.Name.Contains(request.QueryOptions.Name)).AsQueryable(); } - if (String.IsNullOrEmpty(request.QueryOptions.Reference) == false) - { + if (String.IsNullOrEmpty(request.QueryOptions.Reference) == false) { merchantWithAddresses = merchantWithAddresses.Where(m => m.Merchant.Reference == request.QueryOptions.Reference).AsQueryable(); } - if (request.QueryOptions.SettlementSchedule > 0) - { + if (request.QueryOptions.SettlementSchedule > 0) { merchantWithAddresses = merchantWithAddresses.Where(m => m.Merchant.SettlementSchedule == request.QueryOptions.SettlementSchedule).AsQueryable(); } - if (String.IsNullOrEmpty(request.QueryOptions.Region) == false) - { + if (String.IsNullOrEmpty(request.QueryOptions.Region) == false) { merchantWithAddresses = merchantWithAddresses.Where(m => m.Address.Region.Contains(request.QueryOptions.Region)).AsQueryable(); } - if (String.IsNullOrEmpty(request.QueryOptions.PostCode) == false) - { + if (String.IsNullOrEmpty(request.QueryOptions.PostCode) == false) { merchantWithAddresses = merchantWithAddresses.Where(m => m.Address.PostalCode == request.QueryOptions.PostCode).AsQueryable(); } @@ -1174,7 +1025,6 @@ public async Task>> GetMerchants(MerchantQueries.GetMercha Town = queryResult.Address.Town, Country = queryResult.Address.Country, MerchantReportingId = queryResult.Merchant.MerchantReportingId - }); } @@ -1182,7 +1032,7 @@ public async Task>> GetMerchants(MerchantQueries.GetMercha } public async Task> GetMerchant(MerchantQueries.GetMerchantQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; @@ -1219,33 +1069,32 @@ public async Task> GetMerchant(MerchantQueries.GetMerchantQuery var merchant = merchantQueryResult.Data; // Ok now enumerate the results - Merchant result = new Merchant - { - Balance = 0, - CreatedDateTime = merchant.CreatedDateTime, - Name = merchant.Name, - Reference = merchant.Reference, - MerchantId = merchant.MerchantId, - MerchantReportingId = merchant.MerchantReportingId, - SettlementSchedule = merchant.SettlementSchedule, - AddressId = merchant.AddressInfo.AddressId, - AddressLine1 = merchant.AddressInfo.AddressLine1, - AddressLine2 = merchant.AddressInfo.AddressLine2, - Town = merchant.AddressInfo.Town, - Region = merchant.AddressInfo.Region, - PostCode = merchant.AddressInfo.PostalCode, - Country = merchant.AddressInfo.Country, - ContactId = merchant.ContactInfo.ContactId, - ContactName = merchant.ContactInfo.Name, - ContactEmail = merchant.ContactInfo.EmailAddress, - ContactPhone = merchant.ContactInfo.PhoneNumber - }; + Merchant result = new Merchant { + Balance = 0, + CreatedDateTime = merchant.CreatedDateTime, + Name = merchant.Name, + Reference = merchant.Reference, + MerchantId = merchant.MerchantId, + MerchantReportingId = merchant.MerchantReportingId, + SettlementSchedule = merchant.SettlementSchedule, + AddressId = merchant.AddressInfo.AddressId, + AddressLine1 = merchant.AddressInfo.AddressLine1, + AddressLine2 = merchant.AddressInfo.AddressLine2, + Town = merchant.AddressInfo.Town, + Region = merchant.AddressInfo.Region, + PostCode = merchant.AddressInfo.PostalCode, + Country = merchant.AddressInfo.Country, + ContactId = merchant.ContactInfo.ContactId, + ContactName = merchant.ContactInfo.Name, + ContactEmail = merchant.ContactInfo.EmailAddress, + ContactPhone = merchant.ContactInfo.PhoneNumber + }; return Result.Success(result); } public async Task>> GetMerchantOperators(MerchantQueries.GetMerchantOperatorsQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; @@ -1274,28 +1123,27 @@ public async Task>> GetMerchantOperators(MerchantQ } public async Task>> GetMerchantContracts(MerchantQueries.GetMerchantContractsQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - var merchantContractsQuery = context.MerchantContracts.Where(mo => mo.MerchantId == request.MerchantId && mo.IsDeleted == false) - .Select(mc => new { - mc.ContractId, - mc.IsDeleted, - mc.MerchantId, - ContractInfo = context.Contracts.Where(c => c.ContractId == mc.ContractId).Select(ma => new { - ma.Description, - OperatorName = context.Operators.Where(o => o.OperatorId == ma.OperatorId).Select(p => p.Name).Single(), - Products = context.ContractProducts.Where(cp => cp.ContractId == mc.ContractId).Select(cp => new { - cp.DisplayText, - cp.ProductName, - cp.ContractProductId, - cp.ProductType, - cp.Value - }).ToList() - // Add more properties as needed - }).SingleOrDefault() - }); + var merchantContractsQuery = context.MerchantContracts.Where(mo => mo.MerchantId == request.MerchantId && mo.IsDeleted == false).Select(mc => new { + mc.ContractId, + mc.IsDeleted, + mc.MerchantId, + ContractInfo = context.Contracts.Where(c => c.ContractId == mc.ContractId).Select(ma => new { + ma.Description, + OperatorName = context.Operators.Where(o => o.OperatorId == ma.OperatorId).Select(p => p.Name).Single(), + Products = context.ContractProducts.Where(cp => cp.ContractId == mc.ContractId).Select(cp => new { + cp.DisplayText, + cp.ProductName, + cp.ContractProductId, + cp.ProductType, + cp.Value + }).ToList() + // Add more properties as needed + }).SingleOrDefault() + }); var merchantContractsQueryResult = await ExecuteQuerySafeToList(merchantContractsQuery, cancellationToken, "Error getting merchant devices"); @@ -1305,10 +1153,8 @@ public async Task>> GetMerchantContracts(MerchantQ var merchantContracts = merchantContractsQueryResult.Data; List result = new(); - foreach (var merchantContract in merchantContracts) - { - var c = new MerchantContract - { + foreach (var merchantContract in merchantContracts) { + var c = new MerchantContract { ContractId = merchantContract.ContractId, ContractName = merchantContract.ContractInfo.Description, IsDeleted = merchantContract.IsDeleted, @@ -1336,7 +1182,7 @@ public async Task>> GetMerchantContracts(MerchantQ } public async Task>> GetMerchantDevices(MerchantQueries.GetMerchantDevicesQuery request, - CancellationToken cancellationToken) { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; @@ -1349,50 +1195,30 @@ public async Task>> GetMerchantDevices(MerchantQueri var merchantDevices = merchantDevicesQueryResult.Data; List result = new(); - foreach (TransactionProcessor.Database.Entities.MerchantDevice merchantDevice in merchantDevices) - { - result.Add(new MerchantDevice - { - DeviceId = merchantDevice.DeviceId, - DeviceIdentifier = merchantDevice.DeviceIdentifier, - IsDeleted = false, - MerchantId = merchantDevice.MerchantId - }); + foreach (TransactionProcessor.Database.Entities.MerchantDevice merchantDevice in merchantDevices) { + result.Add(new MerchantDevice { DeviceId = merchantDevice.DeviceId, DeviceIdentifier = merchantDevice.DeviceIdentifier, IsDeleted = false, MerchantId = merchantDevice.MerchantId }); } return Result.Success(result); } public async Task> GetProductPerformanceReport(TransactionQueries.ProductPerformanceQuery request, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); await using EstateManagementContext context = resolvedContext.Context; - var grandTotalAmountQuery = - (from t in context.Transactions - where t.TransactionType == "Sale" - && t.TransactionDate >= new DateTime(2025, 12, 21) - && t.TransactionDate <= new DateTime(2025, 12, 25) - select t.TransactionAmount); + var grandTotalAmountQuery = (from t in context.Transactions where t.TransactionType == "Sale" && t.TransactionDate >= new DateTime(2025, 12, 21) && t.TransactionDate <= new DateTime(2025, 12, 25) select t.TransactionAmount); var grandTotalAmountResult = await ExecuteQuerySafeSum(grandTotalAmountQuery, cancellationToken); if (grandTotalAmountResult.IsFailed) return ResultHelpers.CreateFailure(grandTotalAmountResult); var grandTotalAmount = grandTotalAmountResult.Data; - var query = - from t in context.Transactions - join cp in context.ContractProducts - on new { t.ContractProductId, t.ContractId } - equals new { cp.ContractProductId, cp.ContractId } - join c in context.Contracts - on t.ContractId equals c.ContractId - where t.TransactionType == "Sale" - && t.TransactionDate >= request.StartDate - && t.TransactionDate <= request.EndDate - group t by new - { + var query = from t in context.Transactions + join cp in context.ContractProducts on new { t.ContractProductId, t.ContractId } equals new { cp.ContractProductId, cp.ContractId } + join c in context.Contracts on t.ContractId equals c.ContractId + where t.TransactionType == "Sale" && t.TransactionDate >= request.StartDate && t.TransactionDate <= request.EndDate + group t by new { cp.ProductName, cp.ContractProductId, cp.ContractProductReportingId, @@ -1400,8 +1226,7 @@ on t.ContractId equals c.ContractId c.ContractReportingId } into g - select new - { + select new { g.Key.ProductName, g.Key.ContractProductId, g.Key.ContractProductReportingId, @@ -1409,10 +1234,7 @@ into g g.Key.ContractReportingId, TransactionCount = g.Count(), TotalAmount = g.Sum(x => x.TransactionAmount), - PercentOfTotalAmount = - grandTotalAmount == 0 - ? 0 - : 100.0m * g.Sum(x => x.TransactionAmount) / grandTotalAmount + PercentOfTotalAmount = grandTotalAmount == 0 ? 0 : 100.0m * g.Sum(x => x.TransactionAmount) / grandTotalAmount }; var queryResult = await ExecuteQuerySafeToList(query, cancellationToken); @@ -1420,16 +1242,10 @@ into g return ResultHelpers.CreateFailure(queryResult); var queryResults = queryResult.Data; if (queryResults.Any() == false) - return Result.Success(new ProductPerformanceResponse() - { - Summary = new ProductPerformanceSummary(), - ProductDetails = new List() - }); + return Result.Success(new ProductPerformanceResponse() { Summary = new ProductPerformanceSummary(), ProductDetails = new List() }); - ProductPerformanceResponse response = new ProductPerformanceResponse() - { - ProductDetails = queryResults.Select(q => new ProductPerformanceDetail - { + ProductPerformanceResponse response = new ProductPerformanceResponse() { + ProductDetails = queryResults.Select(q => new ProductPerformanceDetail { ProductName = q.ProductName, ProductId = q.ContractProductId, ProductReportingId = q.ContractProductReportingId, @@ -1439,13 +1255,7 @@ into g TransactionValue = q.TotalAmount, PercentageOfTotal = q.PercentOfTotalAmount }).ToList(), - Summary = new ProductPerformanceSummary - { - TotalCount = queryResults.Sum(q => q.TransactionCount), - TotalValue = queryResults.Sum(q => q.TotalAmount), - AveragePerProduct = this.SafeDivide(queryResults.Sum(q => q.TotalAmount), queryResults.Count), - TotalProducts = queryResults.Count() - } + Summary = new ProductPerformanceSummary { TotalCount = queryResults.Sum(q => q.TransactionCount), TotalValue = queryResults.Sum(q => q.TotalAmount), AveragePerProduct = this.SafeDivide(queryResults.Sum(q => q.TotalAmount), queryResults.Count), TotalProducts = queryResults.Count() } }; return Result.Success(response); @@ -1460,59 +1270,123 @@ public async Task>> GetTodaysSalesByHour(Transact IQueryable comparisonSales = this.BuildComparisonSalesQuery(context, request.comparisonDate); // First we need to get a value of todays sales - var todaysSalesByHour = await (from t in todaysSales - group t.TransactionAmount by t.Hour into g select new { Hour = g.Key, TotalSalesCount = g.Count(), TotalSalesValue = g.Sum() }).ToListAsync(cancellationToken); - - var comparisonSalesByHour = await (from t in comparisonSales group t.TransactionAmount by t.Hour into g select new { Hour = g.Key, TotalSalesCount = g.Count(), TotalSalesValue = g.Sum() }).ToListAsync(cancellationToken); - - var response = - ( - from today in todaysSalesByHour - join comparison in comparisonSalesByHour - on today.Hour equals comparison.Hour into compGroup - from comparison in compGroup.DefaultIfEmpty() - select new TodaysSalesByHour - { - Hour = today.Hour.Value, - TodaysSalesCount = today.TotalSalesCount, - TodaysSalesValue = today.TotalSalesValue, - ComparisonSalesCount = comparison?.TotalSalesCount ?? 0, - ComparisonSalesValue = comparison?.TotalSalesValue ?? 0 - } - ) - .Union - ( - from comparison in comparisonSalesByHour - join today in todaysSalesByHour - on comparison.Hour equals today.Hour into todayGroup - from today in todayGroup.DefaultIfEmpty() - where today == null - select new TodaysSalesByHour - { - Hour = comparison.Hour.Value, - TodaysSalesCount = 0, - TodaysSalesValue = 0, - ComparisonSalesCount = comparison.TotalSalesCount, - ComparisonSalesValue = comparison.TotalSalesValue - } - ) - .ToList(); + var todaysSalesByHourQuery = (from t in todaysSales group t.TransactionAmount by t.Hour into g select new { Hour = g.Key, TotalSalesCount = g.Count(), TotalSalesValue = g.Sum() }); + var todaysSalesByHourQueryResult = await ExecuteQuerySafeToList(todaysSalesByHourQuery, cancellationToken); + if (todaysSalesByHourQueryResult.IsFailed) + return ResultHelpers.CreateFailure(todaysSalesByHourQueryResult); + var todaysSalesByHour = todaysSalesByHourQueryResult.Data; + + var comparisonSalesByHourQuery = (from t in comparisonSales group t.TransactionAmount by t.Hour into g select new { Hour = g.Key, TotalSalesCount = g.Count(), TotalSalesValue = g.Sum() }); + var comparisonSalesByHourQueryResult = await ExecuteQuerySafeToList(comparisonSalesByHourQuery, cancellationToken); + if (comparisonSalesByHourQueryResult.IsFailed) + return ResultHelpers.CreateFailure(comparisonSalesByHourQueryResult); + var comparisonSalesByHour = comparisonSalesByHourQueryResult.Data; + + var response = (from today in todaysSalesByHour + join comparison in comparisonSalesByHour on today.Hour equals comparison.Hour into compGroup + from comparison in compGroup.DefaultIfEmpty() + select new TodaysSalesByHour { + Hour = today.Hour.Value, + TodaysSalesCount = today.TotalSalesCount, + TodaysSalesValue = today.TotalSalesValue, + ComparisonSalesCount = comparison?.TotalSalesCount ?? 0, + ComparisonSalesValue = comparison?.TotalSalesValue ?? 0 + }).Union(from comparison in comparisonSalesByHour + join today in todaysSalesByHour on comparison.Hour equals today.Hour into todayGroup + from today in todayGroup.DefaultIfEmpty() + where today == null + select new TodaysSalesByHour { + Hour = comparison.Hour.Value, + TodaysSalesCount = 0, + TodaysSalesValue = 0, + ComparisonSalesCount = comparison.TotalSalesCount, + ComparisonSalesValue = comparison.TotalSalesValue + }).ToList(); return Result.Success(response); } - private IQueryable BuildTodaySalesQuery(EstateManagementContext context) { - return from t in context.TodayTransactions where t.IsAuthorised && t.TransactionType == "Sale" - && t.TransactionDate == DateTime.Now.Date && t.TransactionTime <= DateTime.Now.TimeOfDay - select t; - } + public async Task> GetTodaysSettlement(SettlementQueries.TodaysSettlementQuery request, + CancellationToken cancellationToken) { + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); + await using EstateManagementContext context = resolvedContext.Context; + + IQueryable todaySettlementData = this.BuildTodaySettlementQuery(context, DateTime.Now); + IQueryable comparisonSettlementData = this.BuildComparisonSettlementQuery(context, request.ComparisonDate); + + DatabaseProjections.SettlementGroupProjection todaySettlement = await this.GetSettlementSummary(todaySettlementData, cancellationToken); + DatabaseProjections.SettlementGroupProjection comparisonSettlement = await this.GetSettlementSummary(comparisonSettlementData, cancellationToken); - private IQueryable BuildComparisonSalesQuery(EstateManagementContext context, - DateTime comparisonDate) { - return from t in context.TransactionHistory where t.IsAuthorised && t.TransactionType == "Sale" && t.TransactionDate == comparisonDate && t.TransactionTime <= DateTime.Now.TimeOfDay select t; + TodaysSettlement response = new() + { + ComparisonSettlementCount = comparisonSettlement.SettledCount, + ComparisonSettlementValue = comparisonSettlement.SettledValue, + ComparisonPendingSettlementCount = comparisonSettlement.UnSettledCount, + ComparisonPendingSettlementValue = comparisonSettlement.UnSettledValue, + TodaysSettlementCount = todaySettlement.SettledCount, + TodaysSettlementValue = todaySettlement.SettledValue, + TodaysPendingSettlementCount = todaySettlement.UnSettledCount, + TodaysPendingSettlementValue = todaySettlement.UnSettledValue + }; + + return response; } - #endregion -} + private async Task GetSettlementSummary(IQueryable query, + CancellationToken cancellationToken) { + // Get the settleed fees summary + DatabaseProjections.SettlementGroupProjection summary = await BuildSettlementSummaryQuery(query).SingleOrDefaultAsync(cancellationToken); + + return new DatabaseProjections.SettlementGroupProjection { SettledCount = summary.SettledCount, SettledValue = summary.SettledValue, UnSettledCount = summary.UnSettledCount, UnSettledValue = summary.UnSettledValue }; + } + + private async Task GetSettlementSummary( + IQueryable query, + CancellationToken cancellationToken) + { + + // Get the settleed fees summary + DatabaseProjections.SettlementGroupProjection summary = await BuildSettlementSummaryQuery(query).SingleOrDefaultAsync(cancellationToken); + + return new DatabaseProjections.SettlementGroupProjection + { + SettledCount = summary.SettledCount, + SettledValue = summary.SettledValue, + UnSettledCount = summary.UnSettledCount, + UnSettledValue = summary.UnSettledValue + }; + } + + private static IQueryable BuildSettlementSummaryQuery(IQueryable query) { + return query.GroupBy(_ => 1).Select(g => new DatabaseProjections.SettlementGroupProjection { SettledCount = g.Count(x => x.Fee.IsSettled), SettledValue = g.Where(x => x.Fee.IsSettled).Sum(x => x.Fee.CalculatedValue), UnSettledCount = g.Count(x => !x.Fee.IsSettled), UnSettledValue = g.Where(x => !x.Fee.IsSettled).Sum(x => x.Fee.CalculatedValue) }); + } + + private static IQueryable BuildSettlementSummaryQuery(IQueryable query) { + return query.GroupBy(_ => 1).Select(g => new DatabaseProjections.SettlementGroupProjection { SettledCount = g.Count(x => x.Fee.IsSettled), SettledValue = g.Where(x => x.Fee.IsSettled).Sum(x => x.Fee.CalculatedValue), UnSettledCount = g.Count(x => !x.Fee.IsSettled), UnSettledValue = g.Where(x => !x.Fee.IsSettled).Sum(x => x.Fee.CalculatedValue) }); + } + + private IQueryable BuildTodaySettlementQuery(EstateManagementContext context, + DateTime settlementDate) { + IQueryable settlementData = from s in context.Settlements join f in context.MerchantSettlementFees on s.SettlementId equals f.SettlementId join t in context.TodayTransactions on f.TransactionId equals t.TransactionId where s.SettlementDate == settlementDate select new DatabaseProjections.TodaySettlementTransactionProjection { Fee = f, Txn = t }; + return settlementData; + } + + private IQueryable BuildComparisonSettlementQuery(EstateManagementContext context, + DateTime settlementDate) { + IQueryable settlementData = from s in context.Settlements join f in context.MerchantSettlementFees on s.SettlementId equals f.SettlementId join t in context.TransactionHistory on f.TransactionId equals t.TransactionId where s.SettlementDate == settlementDate.Date select new DatabaseProjections.ComparisonSettlementTransactionProjection { Fee = f, Txn = t }; + return settlementData; + } + + private IQueryable BuildTodaySalesQuery(EstateManagementContext context) { + return from t in context.TodayTransactions where t.IsAuthorised && t.TransactionType == "Sale" && t.TransactionDate == DateTime.Now.Date && t.TransactionTime <= DateTime.Now.TimeOfDay select t; + } + + private IQueryable BuildComparisonSalesQuery(EstateManagementContext context, + DateTime comparisonDate) { + return from t in context.TransactionHistory where t.IsAuthorised && t.TransactionType == "Sale" && t.TransactionDate == comparisonDate && t.TransactionTime <= DateTime.Now.TimeOfDay select t; + } + + #endregion + } diff --git a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs index 04ae197..70e6477 100644 --- a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs +++ b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs @@ -5,6 +5,18 @@ namespace EstateReportingAPI.BusinessLogic.RequestHandlers; +public class SettlementRequestHandler : IRequestHandler> +{ + private readonly IReportingManager Manager; + public SettlementRequestHandler(IReportingManager manager) { + this.Manager = manager; + } + public async Task> Handle(SettlementQueries.TodaysSettlementQuery request, + CancellationToken cancellationToken) { + return await this.Manager.GetTodaysSettlement(request, cancellationToken); + } +} + public class TransactionRequestHandler : IRequestHandler>, IRequestHandler>, IRequestHandler>, diff --git a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs index ddb7fbb..914a1a9 100644 --- a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs +++ b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs @@ -1070,4 +1070,146 @@ public async Task TransactionsEndpoint_TodaysSalesByHour_SummaryDataReturned() } } +} + + +public class SettlmentsEndpointTests : ControllerTestsBase { + private String BaseRoute = "api/settlements"; + + public SettlmentsEndpointTests(ITestOutputHelper testOutputHelper) { + this.TestOutputHelper = testOutputHelper; + } + + + protected override async Task ClearStandingData() { + + } + + protected override async Task SetupStandingData() { + Stopwatch sw = Stopwatch.StartNew(); + this.TestOutputHelper.WriteLine("Setting up standing data"); + + // Estates + await this.helper.AddEstate("Test Estate", "Ref1"); + sw.Stop(); + this.TestOutputHelper.WriteLine($"Setup Estate {sw.ElapsedMilliseconds}ms"); + sw.Restart(); + + // Operators + await this.helper.AddOperator("Test Estate", "Safaricom"); + await this.helper.AddOperator("Test Estate", "Voucher"); + await this.helper.AddOperator("Test Estate", "PataPawa PostPay"); + await this.helper.AddOperator("Test Estate", "PataPawa PrePay"); + + sw.Stop(); + this.TestOutputHelper.WriteLine($"Setup Operators {sw.ElapsedMilliseconds}ms"); + sw.Restart(); + // Merchants + await this.helper.AddMerchant("Test Estate", "Test Merchant 1", DateTime.MinValue, DateTime.MinValue, default, default); + await this.helper.AddMerchant("Test Estate", "Test Merchant 2", DateTime.MinValue, DateTime.MinValue, default, default); + await this.helper.AddMerchant("Test Estate", "Test Merchant 3", DateTime.MinValue, DateTime.MinValue, default, default); + await this.helper.AddMerchant("Test Estate", "Test Merchant 4", DateTime.MinValue, DateTime.MinValue, default, default); + sw.Stop(); + this.TestOutputHelper.WriteLine($"Setup Merchants {sw.ElapsedMilliseconds}ms"); + sw.Restart(); + + // Contracts & Products + List<(string productName, int productType, decimal? value)> safaricomProductList = new() { ("200 KES Topup", 0, 200.00m), ("100 KES Topup", 0, 100.00m), ("50 KES Topup", 0, 50.00m), ("Custom", 0, null) }; + await this.helper.AddContractWithProducts("Test Estate", "Safaricom Contract", "Safaricom", safaricomProductList); + + List<(string productName, int productType, decimal? value)> voucherProductList = new() { ("10 KES Voucher", 0, 10.00m), ("Custom", 0, null) }; + await this.helper.AddContractWithProducts("Test Estate", "Healthcare Centre 1 Contract", "Voucher", voucherProductList); + + List<(string productName, int productType, decimal? value)> postPayProductList = new() { ("Post Pay Bill Pay", 0, null) }; + await this.helper.AddContractWithProducts("Test Estate", "PataPawa PostPay Contract", "PataPawa PostPay", postPayProductList); + + List<(string productName, int productType, decimal? value)> prePayProductList = new() { ("Pre Pay Bill Pay", 0, null) }; + await this.helper.AddContractWithProducts("Test Estate", "PataPawa PrePay Contract", "PataPawa PrePay", prePayProductList); + + sw.Stop(); + this.TestOutputHelper.WriteLine($"Setup Contracts {sw.ElapsedMilliseconds}ms"); + sw.Restart(); + + // Response Codes + await this.helper.AddResponseCode(0, "Success"); + await this.helper.AddResponseCode(1000, "Unknown Device"); + await this.helper.AddResponseCode(1001, "Unknown Estate"); + await this.helper.AddResponseCode(1002, "Unknown Merchant"); + await this.helper.AddResponseCode(1003, "No Devices Configured"); + + sw.Stop(); + this.TestOutputHelper.WriteLine($"Setup Response Codes {sw.ElapsedMilliseconds}ms"); + sw.Restart(); + + this.merchantsList = this.context.Merchants.Select(m => m).ToList(); + + this.contractList = this.context.Contracts.Join(this.context.Operators, c => c.OperatorId, o => o.OperatorId, (c, + o) => new { c.ContractId, c.Description, o.OperatorId, o.Name }).ToList().Select(x => (x.ContractId, x.Description, x.OperatorId, x.Name)).ToList(); + + var query1 = this.context.Contracts.GroupJoin(this.context.ContractProducts, c => c.ContractId, cp => cp.ContractId, (c, + productGroup) => new { c.ContractId, Products = productGroup.Select(p => new { p.ContractProductReportingId, p.ContractProductId, p.ProductName, p.Value }).OrderBy(p => p.ContractProductId).Select(p => new { p.ContractProductId, p.ProductName, p.Value, p.ContractProductReportingId }).ToList() }).ToList(); + + this.contractProducts = query1.ToDictionary(item => item.ContractId, item => item.Products.Select(i => (i.ContractProductId, i.ProductName, i.Value, i.ContractProductReportingId)).ToList()); + + this.operatorsList = this.context.Operators.ToList(); + + sw.Stop(); + this.TestOutputHelper.WriteLine($"Data Caching {sw.ElapsedMilliseconds}ms"); + sw.Restart(); + } + + [Fact] + public async Task SettlementEndpoints_TodaysSettlement_SettlementReturned() + { + int overallTodaysSettlementTransactionCount = 0; + int overallTodaysPendingSettlementTransactionCount = 0; + + int overallComparisonSettlementTransactionCount = 0; + int overallComparisonPendingSettlementTransactionCount = 0; + List<(decimal settledTransactions, decimal pendingSettlementTransactions, decimal settlementFees, decimal pendingSettlementFees)> todayOverallTotals = new(); + List<(decimal settledTransactions, decimal pendingSettlementTransactions, decimal settlementFees, decimal pendingSettlementFees)> comparisonOverallTotals = new(); + + DateTime todaysDate = DateTime.Now; + DateTime comparisonDate = DateTime.Now.AddDays(-1); + foreach (var merchant in merchantsList) + { + int todaysSettlementTransactionCount = 5; + int todaysPendingSettlementTransactionCount = 9; + var contract = this.contractList.Single(c => c.operatorName == "Safaricom"); + (decimal settledTransactions, decimal pendingSettlementTransactions, decimal settlementFees, decimal pendingSettlementFees) todayTotals = await helper.AddSettlementRecord(merchant.EstateId, merchant.MerchantId, contract.operatorId, todaysDate, todaysSettlementTransactionCount, todaysPendingSettlementTransactionCount); + todayOverallTotals.Add(todayTotals); + + overallTodaysSettlementTransactionCount += todaysSettlementTransactionCount; + ; + overallTodaysPendingSettlementTransactionCount += todaysPendingSettlementTransactionCount; + + int comparisonSettlementTransactionCount = 12; + int comparisonPendingSettlementTransactionCount = 15; + var comparisonTotals = await helper.AddSettlementRecord(merchant.EstateId, merchant.MerchantId, contract.operatorId, comparisonDate, comparisonSettlementTransactionCount, comparisonPendingSettlementTransactionCount); + comparisonOverallTotals.Add(comparisonTotals); + + overallComparisonSettlementTransactionCount += comparisonSettlementTransactionCount; + overallComparisonPendingSettlementTransactionCount += comparisonPendingSettlementTransactionCount; + } + + await helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date.AddDays(-1)); + await helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date.AddDays(-1)); + await helper.RunTodaysTransactionsSummaryProcessing(todaysDate.Date.AddDays(-1)); + await helper.RunSettlementSummaryProcessing(comparisonDate.Date); + + + var result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todayssettlements?comparisondate={DateTime.Now.AddDays(-1):yyyy-MM-dd}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var todaysSettlement = result.Data; + todaysSettlement.ShouldNotBeNull(); + todaysSettlement.ComparisonSettlementCount.ShouldBe(overallComparisonSettlementTransactionCount); + todaysSettlement.ComparisonSettlementValue.ShouldBe(comparisonOverallTotals.Sum(c => c.settlementFees)); + todaysSettlement.ComparisonPendingSettlementCount.ShouldBe(overallComparisonPendingSettlementTransactionCount); + todaysSettlement.ComparisonPendingSettlementValue.ShouldBe(comparisonOverallTotals.Sum(c => c.pendingSettlementFees)); + + todaysSettlement.TodaysSettlementCount.ShouldBe(overallTodaysSettlementTransactionCount); + todaysSettlement.TodaysSettlementValue.ShouldBe(todayOverallTotals.Sum(c => c.settlementFees)); + todaysSettlement.TodaysPendingSettlementCount.ShouldBe(overallTodaysPendingSettlementTransactionCount); + todaysSettlement.TodaysPendingSettlementValue.ShouldBe(todayOverallTotals.Sum(c => c.pendingSettlementFees)); + } } \ No newline at end of file diff --git a/EstateReportingAPI.Models/Archive/TodaysSettlement.cs b/EstateReportingAPI.Models/TodaysSettlement.cs similarity index 100% rename from EstateReportingAPI.Models/Archive/TodaysSettlement.cs rename to EstateReportingAPI.Models/TodaysSettlement.cs diff --git a/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs b/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs index be160c4..8144ab1 100644 --- a/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs +++ b/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs @@ -4,4 +4,5 @@ public class TransactionSummaryByOperatorResponse { public List Operators { get; set; } public OperatorDetailSummary Summary { get; set; } -} \ No newline at end of file +} + diff --git a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs index a5ce106..b283f35 100644 --- a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs +++ b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs @@ -27,6 +27,9 @@ public MediatorRegistry() { this.AddSingleton>, TransactionRequestHandler>(); this.AddSingleton>>, TransactionRequestHandler>(); + this.AddSingleton>,SettlementRequestHandler>(); + + this.AddSingleton>>, CalendarRequestHandler>(); this.AddSingleton>>, CalendarRequestHandler>(); this.AddSingleton>>, CalendarRequestHandler>(); diff --git a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs index 245cb21..e30d225 100644 --- a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs +++ b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs @@ -5,6 +5,21 @@ namespace EstateReportingAPI.Endpoints; +public static class SettlementEndpoints { + private const string BaseRoute = "api/settlements"; + + public static void MapSettlementEndpoints(this IEndpointRouteBuilder app) { + RouteGroupBuilder group = app.MapGroup(BaseRoute).WithTags("Settlements"); + + Boolean disableAuthorisation = ConfigurationReader.GetValueOrDefault("AppSettings", "DisableAuthorisation", false); + if (disableAuthorisation == false) { + group = group.RequireAuthorization(); + } + + group.MapGet("todayssettlements", SettlementHandler.TodaysSettlements).WithStandardProduces(); + } +} + public static class TransactionEndpoints { private const string BaseRoute = "api/transactions"; diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index bbab578..2cf693c 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -7,6 +7,27 @@ namespace EstateReportingAPI.Handlers; +public static class SettlementHandler { + public static async Task TodaysSettlements([FromHeader] Guid estateId, + [FromQuery] DateTime comparisonDate, + IMediator mediator, + CancellationToken cancellationToken) { + var query = new SettlementQueries.TodaysSettlementQuery(estateId, comparisonDate); + var result = await mediator.Send(query, cancellationToken); + + return ResponseFactory.FromResult(result, r => new TodaysSettlement() { + ComparisonPendingSettlementCount = r.ComparisonPendingSettlementCount, + ComparisonPendingSettlementValue = r.ComparisonPendingSettlementValue, + ComparisonSettlementCount = r.ComparisonSettlementCount, + ComparisonSettlementValue = r.ComparisonSettlementValue, + TodaysPendingSettlementCount = r.TodaysPendingSettlementCount, + TodaysPendingSettlementValue = r.TodaysPendingSettlementValue, + TodaysSettlementCount = r.TodaysSettlementCount, + TodaysSettlementValue = r.TodaysSettlementValue + }); + } +} + public static class TransactionHandler { public static async Task TodaysSales([FromHeader] Guid estateId, [FromQuery] int? merchantReportingId, diff --git a/EstateReportingAPI/Startup.cs b/EstateReportingAPI/Startup.cs index 74fcfdc..b9bdf0a 100644 --- a/EstateReportingAPI/Startup.cs +++ b/EstateReportingAPI/Startup.cs @@ -74,6 +74,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerF endpoints.MapMerchantEndpoints(); endpoints.MapContractEndpoints(); endpoints.MapTransactionEndpoints(); + endpoints.MapSettlementEndpoints(); // Old Endpoints //endpoints.MapFactSettlementsEndpoints();