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();