diff --git a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs index 260dad4..c18b3c0 100644 --- a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs +++ b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs @@ -9,4 +9,5 @@ namespace EstateReportingAPI.BusinessLogic.Queries; public record TransactionQueries { public record TodaysSalesQuery(Guid EstateId, Int32 MerchantReportingId, Int32 OperatorReportingId, DateTime ComparisonDate) : IRequest>; public record TodaysFailedSales(Guid EstateId, DateTime ComparisonDate, String ResponseCode) : IRequest>; + public record TransactionDetailReportQuery(Guid EstateId, TransactionDetailReportRequest Request) : IRequest>; } \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 8714352..abeff19 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -28,7 +28,6 @@ public interface IReportingManager Task GetContract(ContractQueries.GetContractQuery request, CancellationToken cancellationToken); Task GetTodaysFailedSales(TransactionQueries.TodaysFailedSales request, CancellationToken cancellationToken); Task GetTodaysSales(TransactionQueries.TodaysSalesQuery request, CancellationToken cancellationToken); - Task GetEstate(EstateQueries.GetEstateQuery request, CancellationToken cancellationToken); Task> GetEstateOperators(EstateQueries.GetEstateOperatorsQuery request, CancellationToken cancellationToken); Task GetMerchantsTransactionKpis(MerchantQueries.GetTransactionKpisQuery request, CancellationToken cancellationToken); @@ -38,9 +37,11 @@ public interface IReportingManager Task> GetMerchantOperators(MerchantQueries.GetMerchantOperatorsQuery request, CancellationToken cancellationToken); Task> GetMerchantContracts(MerchantQueries.GetMerchantContractsQuery request, CancellationToken cancellationToken); Task> GetMerchantDevices(MerchantQueries.GetMerchantDevicesQuery request, CancellationToken cancellationToken); - Task GetOperator(OperatorQueries.GetOperatorQuery request, CancellationToken cancellationToken); + Task GetTransactionDetailReport(TransactionQueries.TransactionDetailReportQuery request, + CancellationToken cancellationToken); + #endregion } @@ -593,6 +594,95 @@ public async Task GetOperator(OperatorQueries.GetOperatorQuery request return @operator; } + public async Task GetTransactionDetailReport(TransactionQueries.TransactionDetailReportQuery request, + 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 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 + 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.IsAuthorised // equivalent to IsAuthorised = 1 + && t.TransactionDate >= request.Request.StartDate + && t.TransactionDate <= request.Request.EndDate + select new + { + t.TransactionId, + t.TransactionDateTime, + MerchantId = m.MerchantId, + MerchantReportingId = m.MerchantReportingId, + MerchantName = m.Name, + OperatorId = o.OperatorId, + OperatorReportingId = o.OperatorReportingId, + OperatorName = o.Name, + ProductName = cp.ProductName, + ContractProductId = cp.ContractProductId, + ContractProductReportingId = cp.ContractProductReportingId, + TransactionType = t.TransactionType, + Status = t.IsAuthorised ? "Authorised" : "Declined", + Value = t.TransactionAmount, + FeeValue = msf != null ? msf.FeeValue : 0m, + SettlementId = s != null ? s.SettlementId : Guid.Empty, + }; + + // Now apply the filters + 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()) + { + query = query.Where(q => request.Request.Products.Contains(q.ContractProductReportingId)); + } + if (request.Request.Operators != null && request.Request.Operators.Any()) + { + query = query.Where(q => request.Request.Operators.Contains(q.OperatorReportingId)); + } + + // Ok now enumerate the results + var queryResults = await query.ToListAsync(cancellationToken); + if (queryResults.Any() == false) + return response; + + // Now to translate the results + response = new TransactionDetailReportResponse { + Transactions = queryResults.Select(q => new TransactionDetail { + Id = q.TransactionId, + DateTime = q.TransactionDateTime, + MerchantId = q.MerchantId, + MerchantReportingId = q.MerchantReportingId, + Merchant = q.MerchantName, + OperatorId = q.OperatorId, + OperatorReportingId = q.OperatorReportingId, + Operator = q.OperatorName, + Product = q.ProductName, + ProductId = q.ContractProductId, + ProductReportingId = q.ContractProductReportingId, + Type = q.TransactionType, + Status = q.Status, + Value = q.Value, + 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) + } + }; + + return response; + } + public async Task> GetMerchants(MerchantQueries.GetMerchantsQuery request, CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); @@ -844,3 +934,4 @@ private IQueryable BuildComparisonSalesQuery(EstateManagemen #endregion } + diff --git a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs index 445c627..694e98b 100644 --- a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs +++ b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs @@ -6,7 +6,8 @@ namespace EstateReportingAPI.BusinessLogic.RequestHandlers; public class TransactionRequestHandler : IRequestHandler>, - IRequestHandler>{ + IRequestHandler>, +IRequestHandler> { private readonly IReportingManager Manager; public TransactionRequestHandler(IReportingManager manager) { @@ -24,4 +25,10 @@ public async Task> Handle(TransactionQueries.TodaysSalesQuer var result = await this.Manager.GetTodaysSales(request, cancellationToken); return Result.Success(result); } + + public async Task> Handle(TransactionQueries.TransactionDetailReportQuery request, + CancellationToken cancellationToken) { + var result = await this.Manager.GetTransactionDetailReport(request, cancellationToken); + return Result.Success(result); + } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionResult.cs b/EstateReportingAPI.DataTrasferObjects/TransactionResult.cs index dfaee19..1d8e916 100644 --- a/EstateReportingAPI.DataTrasferObjects/TransactionResult.cs +++ b/EstateReportingAPI.DataTrasferObjects/TransactionResult.cs @@ -5,6 +5,20 @@ namespace EstateReportingAPI.DataTransferObjects{ using Newtonsoft.Json; + public class TransactionDetailReportRequest + { + [JsonProperty("operators")] + public List? Operators { get; set; } + [JsonProperty("merchants")] + public List? Merchants { get; set; } + [JsonProperty("products")] + public List? Products { get; set; } + [JsonProperty("start_date")] + public DateTime StartDate { get; set; } + [JsonProperty("end_date")] + public DateTime EndDate { get; set; } + } + public class TransactionResult{ [JsonProperty("transaction_amount")] public Decimal TransactionAmount{ get; set; } @@ -46,7 +60,7 @@ public class TransactionSearchRequest{ [JsonProperty("merchants")] public List? Merchants{ get; set; } - + [JsonProperty("query_date")] public DateTime QueryDate{ get; set; } @@ -87,4 +101,58 @@ public enum GroupByOption{ Product } + public class TransactionDetailReportResponse + { + [JsonProperty("transactions")] + public List Transactions { get; set; } + [JsonProperty("summary")] + public TransactionDetailSummary Summary { get; set; } + } + + public class TransactionDetailSummary + { + [JsonProperty("total_value")] + public Decimal TotalValue { get; set; } + [JsonProperty("total_fees")] + public Decimal TotalFees { get; set; } + [JsonProperty("transaction_count")] + public Int32 TransactionCount { get; set; } + } + + public class TransactionDetail + { + [JsonProperty("id")] + public Guid Id { get; set; } + [JsonProperty("date_time")] + public DateTime DateTime { get; set; } + [JsonProperty("merchant")] + public String Merchant { get; set; } + [JsonProperty("merchant_id")] + public Guid MerchantId { get; set; } + [JsonProperty("merchant_reporting_id")] + public Int32 MerchantReportingId { get; set; } + [JsonProperty("operator")] + public String Operator { get; set; } + [JsonProperty("operator_id")] + public Guid OperatorId { get; set; } + [JsonProperty("operator_reporting_id")] + public Int32 OperatorReportingId { get; set; } + [JsonProperty("product")] + public String Product { get; set; } + [JsonProperty("product_id")] + public Guid ProductId { get; set; } + [JsonProperty("product_reporting_id")] + public Int32 ProductReportingId { get; set; } + [JsonProperty("type")] + public String Type { get; set; } + [JsonProperty("status")] + public String Status { get; set; } + [JsonProperty("value")] + public Decimal Value { get; set; } + [JsonProperty("total_fees")] + public Decimal TotalFees { get; set; } + [JsonProperty("settlement_reference")] + public String SettlementReference { get; set; } + } + } diff --git a/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs b/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs index 07d347a..4603a20 100644 --- a/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs +++ b/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs @@ -12,6 +12,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; using TransactionProcessor.Database.Contexts; using TransactionProcessor.Database.Entities; using Xunit; @@ -26,6 +27,8 @@ using MerchantOperator = EstateReportingAPI.DataTransferObjects.MerchantOperator; using Operator = EstateReportingAPI.DataTransferObjects.Operator; using TodaysSales = EstateReportingAPI.DataTransferObjects.TodaysSales; +using TransactionDetailReportRequest = EstateReportingAPI.DataTransferObjects.TransactionDetailReportRequest; +using TransactionDetailReportResponse = EstateReportingAPI.DataTransferObjects.TransactionDetailReportResponse; namespace EstateReportingAPI.IntegrationTests { public class CalendarEndpointTests : ControllerTestsBase { @@ -448,7 +451,7 @@ public TransactionsEndpointTests(ITestOutputHelper testOutputHelper) { protected override async Task ClearStandingData() { - throw new NotImplementedException(); + } protected override async Task SetupStandingData() { @@ -460,7 +463,7 @@ protected override async Task SetupStandingData() { 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"); @@ -480,28 +483,16 @@ protected override async Task SetupStandingData() { 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) - }; + 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 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) - }; + List<(string productName, int productType, decimal? value)> voucherProductList = new() { ("10 KES Voucher", 0, 10.00m), ("Custom", 0, null) }; await 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) - }; + List<(string productName, int productType, decimal? value)> postPayProductList = new() { ("Post Pay Bill Pay", 0, null) }; await 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) - }; + List<(string productName, int productType, decimal? value)> prePayProductList = new() { ("Pre Pay Bill Pay", 0, null) }; await helper.AddContractWithProducts("Test Estate", "PataPawa PrePay Contract", "PataPawa PrePay", prePayProductList); sw.Stop(); @@ -521,36 +512,15 @@ protected override async Task SetupStandingData() { merchantsList = context.Merchants.Select(m => m).ToList(); - contractList = context.Contracts - .Join( - 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 = context.Contracts - .GroupJoin( - context.ContractProducts, - c => c.ContractId, - cp => cp.ContractId, - (c, productGroup) => new - { - c.ContractId, - Products = productGroup.Select(p => new { p.ContractProductId, p.ProductName, p.Value }) - .OrderBy(p => p.ContractProductId) - .Select(p => new { p.ContractProductId, p.ProductName, p.Value }) - .ToList() - }) - .ToList(); + contractList = context.Contracts.Join(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 = context.Contracts.GroupJoin(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(); - contractProducts = query1.ToDictionary( - item => item.ContractId, - item => item.Products.Select(i => (i.ContractProductId, i.ProductName, i.Value)).ToList() - ); + 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"); @@ -558,8 +528,7 @@ protected override async Task SetupStandingData() { } [Fact] - public async Task FactTransactionsControllerController_TodaysSales_SalesReturned() - { + public async Task TransactionsEndpoint_TodaysSales_SalesReturned() { List? todaysTransactions = new List(); List comparisonDateTransactions = new List(); @@ -568,16 +537,12 @@ public async Task FactTransactionsControllerController_TodaysSales_SalesReturned Dictionary transactionCounts = new() { { "Test Merchant 1", 15 }, { "Test Merchant 2", 18 }, { "Test Merchant 3", 9 }, { "Test Merchant 4", 0 } }; - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { + for (int i = 0; i < transactionCount; i++) { Transaction transaction = await helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); todaysTransactions.Add(transaction); } @@ -588,16 +553,12 @@ public async Task FactTransactionsControllerController_TodaysSales_SalesReturned await this.helper.AddTransactionsX(todaysTransactions); // Comparison Date sales - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { + for (int i = 0; i < transactionCount; i++) { Transaction transaction = await helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); comparisonDateTransactions.Add(transaction); } @@ -623,64 +584,55 @@ public async Task FactTransactionsControllerController_TodaysSales_SalesReturned todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); } - + [Fact] - public async Task FactTransactionsControllerController_TodaysSales_OperatorFilter_SalesReturned() - { + public async Task TransactionsEndpoint_TodaysSales_OperatorFilter_SalesReturned() { List? todaysTransactions = new List(); - List comparisonDateTransactions = new List(); - - DateTime todaysDateTime = DateTime.Now; - DateTime comparisonDate = DateTime.Now.AddDays(-1).AddHours(-1); - - Dictionary transactionCounts = new() { { "Test Merchant 1", 15 }, { "Test Merchant 2", 18 }, { "Test Merchant 3", 9 }, { "Test Merchant 4", 0 } }; - - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { - var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { - var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { - Transaction transaction = await helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); - todaysTransactions.Add(transaction); - } - } - } - } - - await this.helper.AddTransactionsX(todaysTransactions); - - // Comparison Date sales - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { - var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { - var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { - Transaction transaction = await helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); - comparisonDateTransactions.Add(transaction); - } - } - } - } - - await this.helper.AddTransactionsX(comparisonDateTransactions); - - await helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); - await helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); - await helper.RunTodaysTransactionsSummaryProcessing(todaysDateTime.Date); - - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todayssales?comparisonDate={comparisonDate:yyyy-MM-dd}&operatorReportingId=1", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var todaysSales = result.Data; + List comparisonDateTransactions = new List(); + + DateTime todaysDateTime = DateTime.Now; + DateTime comparisonDate = DateTime.Now.AddDays(-1).AddHours(-1); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 15 }, { "Test Merchant 2", 18 }, { "Test Merchant 3", 9 }, { "Test Merchant 4", 0 } }; + + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) { + Transaction transaction = await helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + todaysTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(todaysTransactions); + + // Comparison Date sales + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) { + Transaction transaction = await helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + comparisonDateTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(comparisonDateTransactions); + + await helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); + await helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); + await helper.RunTodaysTransactionsSummaryProcessing(todaysDateTime.Date); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todayssales?comparisonDate={comparisonDate:yyyy-MM-dd}&operatorReportingId=1", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var todaysSales = result.Data; var operatorId = await this.helper.GetOperatorId(1, CancellationToken.None); todaysSales.ComparisonSalesCount.ShouldBe(comparisonDateTransactions.Count(c => c.OperatorId == operatorId)); @@ -690,64 +642,55 @@ public async Task FactTransactionsControllerController_TodaysSales_OperatorFilte todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.OperatorId == operatorId).Sum(c => c.TransactionAmount)); } - + [Fact] - public async Task FactTransactionsControllerController_TodaysSales_MerchantFilter_SalesReturned() - { - List? todaysTransactions = new List(); - List comparisonDateTransactions = new List(); - - DateTime todaysDateTime = DateTime.Now; - DateTime comparisonDate = DateTime.Now.AddDays(-1).AddHours(-1); - - Dictionary transactionCounts = new() { { "Test Merchant 1", 15 }, { "Test Merchant 2", 18 }, { "Test Merchant 3", 9 }, { "Test Merchant 4", 0 } }; - - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { - var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { - var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { - Transaction transaction = await helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); - todaysTransactions.Add(transaction); - } - } - } - } - - await this.helper.AddTransactionsX(todaysTransactions); - - // Comparison Date sales - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { - var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { - var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { - Transaction transaction = await helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); - comparisonDateTransactions.Add(transaction); - } - } - } - } - - await this.helper.AddTransactionsX(comparisonDateTransactions); - - await helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); - await helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); - await helper.RunTodaysTransactionsSummaryProcessing(todaysDateTime.Date); - - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todayssales?comparisonDate={comparisonDate:yyyy-MM-dd}&merchantReportingId=1", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var todaysSales = result.Data; + public async Task TransactionsEndpoint_TodaysSales_MerchantFilter_SalesReturned() { + List? todaysTransactions = new List(); + List comparisonDateTransactions = new List(); + + DateTime todaysDateTime = DateTime.Now; + DateTime comparisonDate = DateTime.Now.AddDays(-1).AddHours(-1); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 15 }, { "Test Merchant 2", 18 }, { "Test Merchant 3", 9 }, { "Test Merchant 4", 0 } }; + + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) { + Transaction transaction = await helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + todaysTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(todaysTransactions); + + // Comparison Date sales + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) { + Transaction transaction = await helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + comparisonDateTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(comparisonDateTransactions); + + await helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); + await helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); + await helper.RunTodaysTransactionsSummaryProcessing(todaysDateTime.Date); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todayssales?comparisonDate={comparisonDate:yyyy-MM-dd}&merchantReportingId=1", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var todaysSales = result.Data; var merchantId = await this.helper.GetMerchantId(1, CancellationToken.None); todaysSales.ComparisonSalesCount.ShouldBe(comparisonDateTransactions.Count(c => c.MerchantId == merchantId)); @@ -758,8 +701,7 @@ public async Task FactTransactionsControllerController_TodaysSales_MerchantFilte } [Fact] - public async Task FactTransactionsControllerController_TodaysFailedSales_SalesReturned() - { + public async Task TransactionsEndpoint_TodaysFailedSales_SalesReturned() { //EstateManagementContext context = new EstateManagementContext(GetLocalConnectionString($"TransactionProcessorReadModel-{TestId.ToString()}")); var todaysTransactions = new List(); @@ -773,16 +715,12 @@ public async Task FactTransactionsControllerController_TodaysFailedSales_SalesRe DateTime comparisonDate = todaysDateTime.AddDays(-1); - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { + for (int i = 0; i < transactionCount; i++) { Transaction transaction = await helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "1009", product.productValue); todaysTransactions.Add(transaction); } @@ -793,16 +731,12 @@ public async Task FactTransactionsControllerController_TodaysFailedSales_SalesRe await helper.AddTransactionsX(todaysTransactions); // Comparison Date sales - foreach (var merchant in merchantsList) - { - foreach (var contract in contractList) - { + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; - foreach ((Guid productId, String productName, Decimal? productValue) product in productList) - { + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; - for (int i = 0; i < transactionCount; i++) - { + for (int i = 0; i < transactionCount; i++) { Transaction transaction = await helper.BuildTransactionX(comparisonDate.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "1009", product.productValue); comparisonDateTransactions.Add(transaction); } @@ -828,6 +762,285 @@ public async Task FactTransactionsControllerController_TodaysFailedSales_SalesRe todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count); todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); } + + [Fact] + public async Task TransactionsEndpoint_TransactionDetailReport_NoFilters_TransactionsReturned() { + + var transactions = new List(); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 3 }, { "Test Merchant 2", 6 }, { "Test Merchant 3", 2 }, { "Test Merchant 4", 0 } }; + + // Get a set of dates for the transactions + List transactionDates = new() { + DateTime.Now.Date, + DateTime.Now.Date.AddDays(-1), + DateTime.Now.Date.AddDays(-2), + DateTime.Now.Date.AddDays(-3), + DateTime.Now.Date.AddDays(-4), + DateTime.Now.Date.AddDays(-5), + }; + + foreach (DateTime transactionDate in transactionDates) { + foreach (var merchant in merchantsList) { + foreach (var contract in contractList) { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) { + Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await helper.AddTransactionsX(transactions); + + List orderedDates = transactionDates.OrderBy(x => x).ToList(); + TransactionDetailReportRequest request = new TransactionDetailReportRequest { + StartDate = orderedDates.First(), + EndDate = orderedDates.Last(), + Merchants = [], + Operators = [], + Products = [] + }; + + String payload = JsonConvert.SerializeObject(request); + + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/transactionDetailReport", payload, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var transactionDetailReportResponse = result.Data; + + transactionDetailReportResponse.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.TransactionCount.ShouldBe(transactions.Count); + transactionDetailReportResponse.Summary.TotalValue.ShouldBe(transactions.Sum(t=> t.TransactionAmount)); + + foreach (Transaction transaction in transactions) { + var foundTxn = transactionDetailReportResponse.Transactions.SingleOrDefault(t => t.Id == transaction.TransactionId); + foundTxn.ShouldNotBeNull(transaction.TransactionId.ToString()); + } + } + + [Fact] + public async Task TransactionsEndpoint_TransactionDetailReport_MerchantFilter_TransactionsReturned() + { + + var transactions = new List(); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 3 }, { "Test Merchant 2", 6 }, { "Test Merchant 3", 2 }, { "Test Merchant 4", 0 } }; + + // Get a set of dates for the transactions + List transactionDates = new() { + DateTime.Now.Date, + DateTime.Now.Date.AddDays(-1), + DateTime.Now.Date.AddDays(-2), + DateTime.Now.Date.AddDays(-3), + DateTime.Now.Date.AddDays(-4), + DateTime.Now.Date.AddDays(-5), + }; + + foreach (DateTime transactionDate in transactionDates) + { + foreach (var merchant in merchantsList) + { + foreach (var contract in contractList) + { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) + { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) + { + Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await helper.AddTransactionsX(transactions); + + var merchantsForFilter = this.merchantsList.Where(m => m.Name == "Test Merchant 1" || m.Name == "Test Merchant 2"); + + List orderedDates = transactionDates.OrderBy(x => x).ToList(); + TransactionDetailReportRequest request = new TransactionDetailReportRequest + { + StartDate = orderedDates.First(), + EndDate = orderedDates.Last(), + Merchants = merchantsForFilter.Select(m=> m.MerchantReportingId).ToList(), + Operators = [], + Products = [] + }; + + String payload = JsonConvert.SerializeObject(request); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/transactionDetailReport", payload, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var transactionDetailReportResponse = result.Data; + + // filter transactions for verification + var filteredTransactions = transactions.Where(t => merchantsForFilter.Select(m => m.MerchantId).Contains(t.MerchantId)); + + transactionDetailReportResponse.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.TransactionCount.ShouldBe(filteredTransactions.Count()); + transactionDetailReportResponse.Summary.TotalValue.ShouldBe(filteredTransactions.Sum(t => t.TransactionAmount)); + + foreach (Transaction transaction in filteredTransactions) + { + var foundTxn = transactionDetailReportResponse.Transactions.SingleOrDefault(t => t.Id == transaction.TransactionId); + foundTxn.ShouldNotBeNull(transaction.TransactionId.ToString()); + } + } + + [Fact] + public async Task TransactionsEndpoint_TransactionDetailReport_OperatorFilter_TransactionsReturned() + { + + var transactions = new List(); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 3 }, { "Test Merchant 2", 6 }, { "Test Merchant 3", 2 }, { "Test Merchant 4", 0 } }; + + // Get a set of dates for the transactions + List transactionDates = new() { + DateTime.Now.Date, + DateTime.Now.Date.AddDays(-1), + DateTime.Now.Date.AddDays(-2), + DateTime.Now.Date.AddDays(-3), + DateTime.Now.Date.AddDays(-4), + DateTime.Now.Date.AddDays(-5), + }; + + foreach (DateTime transactionDate in transactionDates) + { + foreach (var merchant in merchantsList) + { + foreach (var contract in contractList) + { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) + { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) + { + Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await helper.AddTransactionsX(transactions); + + var operatorsForFilter = this.operatorsList.Where(m => m.Name == "Safaricom"); + + List orderedDates = transactionDates.OrderBy(x => x).ToList(); + TransactionDetailReportRequest request = new TransactionDetailReportRequest + { + StartDate = orderedDates.First(), + EndDate = orderedDates.Last(), + Merchants = [], + Operators = operatorsForFilter.Select(o=> o.OperatorReportingId).ToList(), + Products = [] + }; + + String payload = JsonConvert.SerializeObject(request); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/transactionDetailReport", payload, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var transactionDetailReportResponse = result.Data; + + // filter transactions for verification + var filteredTransactions = transactions.Where(t => operatorsForFilter.Select(m => m.OperatorId).Contains(t.OperatorId)); + + transactionDetailReportResponse.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.TransactionCount.ShouldBe(filteredTransactions.Count()); + transactionDetailReportResponse.Summary.TotalValue.ShouldBe(filteredTransactions.Sum(t => t.TransactionAmount)); + + foreach (Transaction transaction in filteredTransactions) + { + var foundTxn = transactionDetailReportResponse.Transactions.SingleOrDefault(t => t.Id == transaction.TransactionId); + foundTxn.ShouldNotBeNull(transaction.TransactionId.ToString()); + } + } + + [Fact] + public async Task TransactionsEndpoint_TransactionDetailReport_ProductFilter_TransactionsReturned() + { + var transactions = new List(); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 3 }, { "Test Merchant 2", 6 }, { "Test Merchant 3", 2 }, { "Test Merchant 4", 0 } }; + + // Get a set of dates for the transactions + List transactionDates = new() { + DateTime.Now.Date, + DateTime.Now.Date.AddDays(-1), + DateTime.Now.Date.AddDays(-2), + DateTime.Now.Date.AddDays(-3), + DateTime.Now.Date.AddDays(-4), + DateTime.Now.Date.AddDays(-5), + }; + + foreach (DateTime transactionDate in transactionDates) + { + foreach (var merchant in merchantsList) + { + foreach (var contract in contractList) + { + var productList = contractProducts.Single(cp => cp.Key == contract.contractId).Value; + foreach ((Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId) product in productList) + { + var transactionCount = transactionCounts.Single(m => m.Key == merchant.Name).Value; + for (int i = 0; i < transactionCount; i++) + { + Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await helper.AddTransactionsX(transactions); + + IEnumerable<(Guid productId, String productName, Decimal? productValue, Int32 contractProductReportingId)> productsForFilter = this.contractProducts.SelectMany(cp => cp.Value).Where(p => p.productName == "100 KES Topup"); + + List orderedDates = transactionDates.OrderBy(x => x).ToList(); + TransactionDetailReportRequest request = new TransactionDetailReportRequest + { + StartDate = orderedDates.First(), + EndDate = orderedDates.Last(), + Merchants = [], + Operators = [], + Products = productsForFilter.Select(c => c.contractProductReportingId).ToList(), + }; + + String payload = JsonConvert.SerializeObject(request); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/transactionDetailReport", payload, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var transactionDetailReportResponse = result.Data; + + // filter transactions for verification + var filteredTransactions = transactions.Where(t => productsForFilter.Select(m => m.productId).Contains(t.ContractProductId)); + + transactionDetailReportResponse.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.ShouldNotBeNull(); + transactionDetailReportResponse.Summary.TransactionCount.ShouldBe(filteredTransactions.Count()); + transactionDetailReportResponse.Summary.TotalValue.ShouldBe(filteredTransactions.Sum(t => t.TransactionAmount)); + + foreach (Transaction transaction in filteredTransactions) + { + var foundTxn = transactionDetailReportResponse.Transactions.SingleOrDefault(t => t.Id == transaction.TransactionId); + foundTxn.ShouldNotBeNull(transaction.TransactionId.ToString()); + } + } } } diff --git a/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs b/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs index 26a35fd..3538ccc 100644 --- a/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs +++ b/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs @@ -24,8 +24,9 @@ namespace EstateReportingAPI.IntegrationTests; public abstract class ControllerTestsBase : IAsyncLifetime { protected List merchantsList; + protected List operatorsList; protected List<(Guid contractId, String contractName, Guid operatorId, String operatorName)> contractList; - protected Dictionary> contractProducts; + protected Dictionary> contractProducts; protected DatabaseHelper helper; protected ITestOutputHelper TestOutputHelper; protected DockerHelper DockerHelper; @@ -85,7 +86,7 @@ public virtual async Task DisposeAsync() internal async Task> CreateAndSendHttpRequestMessage(String url, String payload, CancellationToken cancellationToken) { - HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, url); + HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Post, url); requestMessage.Headers.Add("estateId", this.TestId.ToString()); requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Test"); if (String.IsNullOrEmpty(payload) == false){ diff --git a/EstateReportingAPI.Models/TransactionDetailReportRequest.cs b/EstateReportingAPI.Models/TransactionDetailReportRequest.cs new file mode 100644 index 0000000..00e949a --- /dev/null +++ b/EstateReportingAPI.Models/TransactionDetailReportRequest.cs @@ -0,0 +1,40 @@ +namespace EstateReportingAPI.Models; + +public class TransactionDetailReportRequest +{ + public List? Operators { get; set; } + public List? Merchants { get; set; } + public List? Products { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } +} + +public class TransactionDetailReportResponse { + public List Transactions { get; set; } + public TransactionDetailSummary Summary { get; set; } +} + +public class TransactionDetailSummary { + public Decimal TotalValue { get; set; } + public Decimal TotalFees { get; set; } + public Int32 TransactionCount { get; set; } +} + +public class TransactionDetail { + public Guid Id { get; set; } + public DateTime DateTime { get; set; } + public String Merchant { get; set; } + public Guid MerchantId { get; set; } + public Int32 MerchantReportingId { get; set; } + public String Operator { get; set; } + public Guid OperatorId { get; set; } + public Int32 OperatorReportingId { get; set; } + public String Product { get; set; } + public Guid ProductId { get; set; } + public Int32 ProductReportingId { get; set; } + public String Type { get; set; } + public String Status { get; set; } + public Decimal Value { get; set; } + public Decimal TotalFees { get; set; } + public String SettlementReference { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs index 6eaffd7..2166b49 100644 --- a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs +++ b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs @@ -21,7 +21,8 @@ public MediatorRegistry() { this.AddSingleton>, TransactionRequestHandler>(); this.AddSingleton>, TransactionRequestHandler>(); - + this.AddSingleton>, TransactionRequestHandler>(); + this.AddSingleton>>, CalendarRequestHandler>(); this.AddSingleton>>, CalendarRequestHandler>(); this.AddSingleton>>, CalendarRequestHandler>(); @@ -39,3 +40,4 @@ public MediatorRegistry() { this.AddSingleton>, ContractRequestHandler>(); } } + diff --git a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs index a8d995c..d60f62f 100644 --- a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs +++ b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs @@ -22,5 +22,7 @@ public static void MapTransactionEndpoints(this IEndpointRouteBuilder app) .WithStandardProduces(); group.MapGet("todaysfailedsales", TransactionHandler.TodaysFailedSales) .WithStandardProduces(); + group.MapPost("transactionDetailReport", TransactionHandler.TransactionDetailReport) + .WithStandardProduces(); } } \ No newline at end of file diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index 18d65ca..e001042 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -1,8 +1,9 @@ using EstateReportingAPI.BusinessLogic.Queries; -using EstateReportingAPI.Models; +using EstateReportingAPI.DataTransferObjects; using MediatR; using Microsoft.AspNetCore.Mvc; using Shared.Results.Web; +using TodaysSales = EstateReportingAPI.Models.TodaysSales; namespace EstateReportingAPI.Handlers; @@ -47,4 +48,39 @@ public static async Task TodaysFailedSales([FromHeader] Guid estateId, TodaysAverageSalesValue = r.TodaysAverageSalesValue, }); } + + public static async Task TransactionDetailReport([FromHeader] Guid estateId, + [FromBody] TransactionDetailReportRequest request, + IMediator mediator, CancellationToken cancellationToken) { + Models.TransactionDetailReportRequest queryRequest = new() { + Merchants = request.Merchants, + Operators = request.Operators, + Products = request.Products, + StartDate = request.StartDate, + EndDate = request.EndDate + }; + + var query = new TransactionQueries.TransactionDetailReportQuery(estateId, queryRequest); + var result = await mediator.Send(query, cancellationToken); + + TransactionDetailReportResponse SuccessFactory (Models.TransactionDetailReportResponse r) => + new TransactionDetailReportResponse { + Summary = new TransactionDetailSummary { TotalFees = r.Summary.TotalFees, TotalValue = r.Summary.TotalValue, TransactionCount = r.Summary.TransactionCount }, + Transactions = r.Transactions.Select(t => new TransactionDetail { + TotalFees = t.TotalFees, + DateTime = t.DateTime, + Id = t.Id, + Merchant = t.Merchant, + Operator = t.Operator, + Product = t.Product, + SettlementReference = t.SettlementReference, + Status = t.Status, + Type = t.Type, + Value = t.Value + }) + .ToList() + }; + + return ResponseFactory.FromResult(result, SuccessFactory); + } } \ No newline at end of file