diff --git a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs index 6956a01..d8a9a96 100644 --- a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs +++ b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs @@ -10,7 +10,7 @@ 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>; - public record TransactionSummaryByMerchantQuery(Guid EstateId, TransactionSummaryByMerchantRequest Request) : IRequest>; - + public record TransactionSummaryByOperatorQuery(Guid EstateId, TransactionSummaryByOperatorRequest Request) : IRequest>; + } \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 4e3457a..5c37e00 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -46,6 +46,9 @@ Task> GetTransactionDetailReport(Transac CancellationToken cancellationToken); Task> GetTransactionSummaryByMerchantReport(TransactionQueries.TransactionSummaryByMerchantQuery request, CancellationToken cancellationToken); + + Task> GetTransactionSummaryByOperatorReport(TransactionQueries.TransactionSummaryByOperatorQuery request, + CancellationToken cancellationToken); #endregion } @@ -974,6 +977,117 @@ into g return response; } + public async Task> GetTransactionSummaryByOperatorReport(TransactionQueries.TransactionSummaryByOperatorQuery request, + CancellationToken cancellationToken) { + TransactionSummaryByOperatorResponse 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 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 + { + t.MerchantId, + m.MerchantReportingId, + MerchantName = m.Name, + t.OperatorId, + o.OperatorReportingId, + OperatorName = o.Name + } + into g + select new + { + g.Key.MerchantId, + g.Key.MerchantReportingId, + g.Key.MerchantName, + g.Key.OperatorId, + g.Key.OperatorReportingId, + g.Key.OperatorName, + TotalCount = g.Count(), + TotalValue = g.Sum(x => x.TransactionAmount), + AuthorisedCount = g.Sum(x => x.IsAuthorised ? 1 : 0), + DeclinedCount = g.Sum(x => x.IsAuthorised ? 0 : 1) + }; + + // 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.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 + }; + + var queryResult = await ExecuteQuerySafeToList(finalQuery, cancellationToken, "Error retrieving transaction summary by operator report"); + + if (queryResult.IsFailed) + return ResultHelpers.CreateFailure(queryResult); + + // Ok now enumerate the results + var queryResults = queryResult.Data; + + if (queryResults.Any() == false) + return new TransactionSummaryByOperatorResponse + { + Summary = new OperatorDetailSummary(), + Operators = new List() + }; + + // Now to translate the results + response = new TransactionSummaryByOperatorResponse + { + Operators = queryResults.Select(q => new OperatorDetail + { + OperatorId = q.OperatorId, + OperatorReportingId = q.OperatorReportingId, + OperatorName = q.OperatorName, + AuthorisedCount = q.AuthorisedCount, + AuthorisedPercentage = q.AuthorisedPercentage, + AverageValue = q.AverageValue, + DeclinedCount = q.DeclinedCount, + 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() + } + }; + + return response; + } + public async Task>> GetMerchants(MerchantQueries.GetMerchantsQuery request, CancellationToken cancellationToken) { using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); @@ -1041,7 +1155,9 @@ public async Task>> GetMerchants(MerchantQueries.GetMercha AddressLine1 = queryResult.Address.AddressLine1, AddressLine2 = queryResult.Address.AddressLine2, Town = queryResult.Address.Town, - Country = queryResult.Address.Country + Country = queryResult.Address.Country, + MerchantReportingId = queryResult.Merchant.MerchantReportingId + }); } diff --git a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs index 85eef6e..8771073 100644 --- a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs +++ b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs @@ -7,8 +7,9 @@ namespace EstateReportingAPI.BusinessLogic.RequestHandlers; public class TransactionRequestHandler : IRequestHandler>, IRequestHandler>, -IRequestHandler>, - IRequestHandler> + IRequestHandler>, + IRequestHandler>, + IRequestHandler> { private readonly IReportingManager Manager; @@ -36,4 +37,10 @@ public async Task> Handle(Transacti { return await this.Manager.GetTransactionSummaryByMerchantReport(request, cancellationToken); } + + public async Task> Handle(TransactionQueries.TransactionSummaryByOperatorQuery request, + CancellationToken cancellationToken) + { + return await this.Manager.GetTransactionSummaryByOperatorReport(request, cancellationToken); + } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportRequest.cs b/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportRequest.cs index 3738726..a78300f 100644 --- a/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportRequest.cs +++ b/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportRequest.cs @@ -16,16 +16,4 @@ public class TransactionDetailReportRequest public DateTime StartDate { get; set; } [JsonProperty("end_date")] public DateTime EndDate { get; set; } -} - -public class TransactionSummaryByMerchantRequest -{ - [JsonProperty("operators")] - public List? Operators { get; set; } - [JsonProperty("merchants")] - public List? Merchants { get; set; } - [JsonProperty("start_date")] - public DateTime StartDate { get; set; } - [JsonProperty("end_date")] - public DateTime EndDate { get; set; } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs b/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs index 67f88ef..aeca40f 100644 --- a/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs +++ b/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs @@ -50,4 +50,47 @@ public class MerchantDetailSummary { public Decimal TotalValue { get; set; } [JsonProperty("average_value")] public Decimal AverageValue { get; set; } +} + + +public class TransactionSummaryByOperatorResponse +{ + [JsonProperty("operators")] + public List Operators { get; set; } + [JsonProperty("summary")] + public OperatorDetailSummary Summary { get; set; } +} + +public class OperatorDetail +{ + [JsonProperty("operator_id")] + public Guid OperatorId { get; set; } + [JsonProperty("operator_reporting_id")] + public Int32 OperatorReportingId { get; set; } + [JsonProperty("operator_name")] + public string OperatorName { get; set; } + [JsonProperty("total_count")] + public Int32 TotalCount { get; set; } + [JsonProperty("total_value")] + public Decimal TotalValue { get; set; } + [JsonProperty("average_value")] + public Decimal AverageValue { get; set; } + [JsonProperty("authorised_count")] + public Int32 AuthorisedCount { get; set; } + [JsonProperty("declined_count")] + public Int32 DeclinedCount { get; set; } + [JsonProperty("authorised_percentage")] + public Decimal AuthorisedPercentage { get; set; } +} + +public class OperatorDetailSummary +{ + [JsonProperty("total_operators")] + public Int32 TotalOperators { get; set; } + [JsonProperty("total_count")] + public Int32 TotalCount { get; set; } + [JsonProperty("total_value")] + public Decimal TotalValue { get; set; } + [JsonProperty("average_value")] + public Decimal AverageValue { get; set; } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantRequest.cs b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantRequest.cs new file mode 100644 index 0000000..f5a0eb0 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +public class TransactionSummaryByMerchantRequest +{ + [JsonProperty("operators")] + public List? Operators { get; set; } + [JsonProperty("merchants")] + public List? Merchants { get; set; } + [JsonProperty("start_date")] + public DateTime StartDate { get; set; } + [JsonProperty("end_date")] + public DateTime EndDate { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorRequest.cs b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorRequest.cs new file mode 100644 index 0000000..f32f0d0 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +public class TransactionSummaryByOperatorRequest +{ + [JsonProperty("operators")] + public List? Operators { get; set; } + [JsonProperty("merchants")] + public List? Merchants { get; set; } + [JsonProperty("start_date")] + public DateTime StartDate { get; set; } + [JsonProperty("end_date")] + public DateTime EndDate { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs b/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs index 40bb2f7..5c7bd92 100644 --- a/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs +++ b/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs @@ -1050,7 +1050,6 @@ public async Task TransactionsEndpoint_TransactionDetailReport_ProductFilter_Tra [Fact] public async Task TransactionsEndpoint_TransactionSummaryByMerchantReport_NoFilters_SummaryDataReturned() { - var transactions = new List(); TransactionProcessor.Database.Entities.Merchant merchant1 = this.merchantsList.SingleOrDefault(m => m.Name == "Test Merchant 1"); @@ -1069,6 +1068,8 @@ public async Task TransactionsEndpoint_TransactionSummaryByMerchantReport_NoFilt DateTime.Now.Date.AddDays(-3) ]; + var merchants = new[] { merchant1, merchant2, merchant3 }; + Dictionary<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32> salesConfig = new Dictionary<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32>(); salesConfig.Add((DateTime.Now.Date, merchant1), 15); salesConfig.Add((DateTime.Now.Date.AddDays(-1), merchant1), 25); @@ -1089,86 +1090,48 @@ public async Task TransactionsEndpoint_TransactionSummaryByMerchantReport_NoFilt foreach (DateTime transactionDate in transactionDates) { - // Build merchant 1 sales - KeyValuePair<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32> config = salesConfig.SingleOrDefault(c => c.Key == (transactionDate, merchant1)); - - for (int i = 0; i < config.Value; i++) { - string responseCode = i switch { - var n when n % 4 == 2 => "1009", // change value on every 4rd iteration - _ => "0000" - }; - Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant1.MerchantId, safaricomContract.operatorId, safaricomContract.contractId, safaricomProduct.productId, responseCode, safaricomProduct.productValue); - transactions.Add(transaction); - - if (!totalCountsByMerchant.ContainsKey(merchant1)) { - totalCountsByMerchant.Add(merchant1, 0); - authorisedCountsByMerchant.Add(merchant1, 0); - declinedCountsByMerchant.Add(merchant1, 0); - } - totalCountsByMerchant[merchant1]++; - if (responseCode == "0000") { - authorisedCountsByMerchant[merchant1]++; - } - else { - declinedCountsByMerchant[merchant1]++; - } - } - - config = salesConfig.SingleOrDefault(c => c.Key == (transactionDate, merchant2)); + foreach (TransactionProcessor.Database.Entities.Merchant merchant in merchants) { - for (int i = 0; i < config.Value; i++) - { - string responseCode = i switch - { - var n when n % 4 == 2 => "1009", // change value on every 4rd iteration - _ => "0000" - }; - Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant2.MerchantId, safaricomContract.operatorId, safaricomContract.contractId, safaricomProduct.productId, responseCode, safaricomProduct.productValue); - transactions.Add(transaction); + // Build merchant sales + KeyValuePair<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32> config = salesConfig.SingleOrDefault(c => c.Key == (transactionDate, merchant)); - if (!totalCountsByMerchant.ContainsKey(merchant2)) - { - totalCountsByMerchant.Add(merchant2, 0); - authorisedCountsByMerchant.Add(merchant2, 0); - declinedCountsByMerchant.Add(merchant2, 0); - } - totalCountsByMerchant[merchant2]++; - if (responseCode == "0000") - { - authorisedCountsByMerchant[merchant2]++; - } - else - { - declinedCountsByMerchant[merchant2]++; - } - } + for (int i = 0; i < config.Value; i++) { + string responseCode = i switch { + var n when n % 4 == 2 => "1009", // change value on every 4rd iteration + _ => "0000" + }; + if (!totalCountsByMerchant.ContainsKey(merchant)) + { + totalCountsByMerchant.Add(merchant, 0); + authorisedCountsByMerchant.Add(merchant, 0); + declinedCountsByMerchant.Add(merchant, 0); + } - config = salesConfig.SingleOrDefault(c => c.Key == (transactionDate, merchant3)); + Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, + safaricomContract.operatorId, safaricomContract.contractId, safaricomProduct.productId, responseCode, safaricomProduct.productValue); + transactions.Add(transaction); + + totalCountsByMerchant[merchant]++; + if (responseCode == "0000") { + authorisedCountsByMerchant[merchant]++; + } + else { + declinedCountsByMerchant[merchant]++; + } - for (int i = 0; i < config.Value; i++) - { - string responseCode = i switch - { - var n when n % 4 == 2 => "1009", // change value on every 4rd iteration - _ => "0000" - }; - Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant3.MerchantId, safaricomContract.operatorId, safaricomContract.contractId, safaricomProduct.productId, responseCode, safaricomProduct.productValue); - transactions.Add(transaction); + Transaction transaction1 = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, + voucherContract.operatorId, voucherContract.contractId, voucherProduct.productId, responseCode, voucherProduct.productValue); + transactions.Add(transaction1); - if (!totalCountsByMerchant.ContainsKey(merchant3)) - { - totalCountsByMerchant.Add(merchant3, 0); - authorisedCountsByMerchant.Add(merchant3, 0); - declinedCountsByMerchant.Add(merchant3, 0); - } - totalCountsByMerchant[merchant3]++; - if (responseCode == "0000") - { - authorisedCountsByMerchant[merchant3]++; - } - else - { - declinedCountsByMerchant[merchant3]++; + totalCountsByMerchant[merchant]++; + if (responseCode == "0000") + { + authorisedCountsByMerchant[merchant]++; + } + else + { + declinedCountsByMerchant[merchant]++; + } } } } @@ -1197,8 +1160,8 @@ public async Task TransactionsEndpoint_TransactionSummaryByMerchantReport_NoFilt transactionSummaryByMerchantResponse.Summary.TotalValue.ShouldBe(transactions.Sum(t => t.TransactionAmount)); transactionSummaryByMerchantResponse.Merchants.Count.ShouldBe(3); - var merchants = new[] { merchant1, merchant2, merchant3 }; - foreach (TransactionProcessor.Database.Entities.Merchant mr in merchants) { + var operators = new List { safaricomContract.operatorId, voucherContract.operatorId }; + foreach (var mr in merchants) { var row = transactionSummaryByMerchantResponse.Merchants.SingleOrDefault(m => m.MerchantId == mr.MerchantId); row.ShouldNotBeNull(); row.MerchantId.ShouldBe(mr.MerchantId); @@ -1209,6 +1172,147 @@ public async Task TransactionsEndpoint_TransactionSummaryByMerchantReport_NoFilt } } + [Fact] + public async Task TransactionsEndpoint_TransactionSummaryByOperatorReport_NoFilters_SummaryDataReturned() + { + + var transactions = new List(); + + TransactionProcessor.Database.Entities.Merchant merchant1 = this.merchantsList.SingleOrDefault(m => m.Name == "Test Merchant 1"); + var merchant2 = this.merchantsList.SingleOrDefault(m => m.Name == "Test Merchant 2"); + var merchant3 = this.merchantsList.SingleOrDefault(m => m.Name == "Test Merchant 3"); + var safaricomContract = this.contractList.SingleOrDefault(c => c.contractName == "Safaricom Contract"); + var voucherContract = this.contractList.SingleOrDefault(c => c.contractName == "Healthcare Centre 1 Contract"); + var safaricomProduct = contractProducts.Single(cp => cp.Key == safaricomContract.contractId).Value.First(); + var voucherProduct = contractProducts.Single(cp => cp.Key == voucherContract.contractId).Value.First(); + + + List transactionDates = [ + DateTime.Now.Date, + DateTime.Now.Date.AddDays(-1), + DateTime.Now.Date.AddDays(-2), + DateTime.Now.Date.AddDays(-3) + ]; + + var merchants = new[] { merchant1, merchant2, merchant3 }; + + Dictionary<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32> salesConfig = new Dictionary<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32>(); + salesConfig.Add((DateTime.Now.Date, merchant1), 15); + salesConfig.Add((DateTime.Now.Date.AddDays(-1), merchant1), 25); + salesConfig.Add((DateTime.Now.Date.AddDays(-2), merchant1), 24); + salesConfig.Add((DateTime.Now.Date.AddDays(-3), merchant1), 19); + salesConfig.Add((DateTime.Now.Date, merchant2), 15); + salesConfig.Add((DateTime.Now.Date.AddDays(-1), merchant2), 25); + salesConfig.Add((DateTime.Now.Date.AddDays(-2), merchant2), 24); + salesConfig.Add((DateTime.Now.Date.AddDays(-3), merchant2), 19); + salesConfig.Add((DateTime.Now.Date, merchant3), 15); + salesConfig.Add((DateTime.Now.Date.AddDays(-1), merchant3), 25); + salesConfig.Add((DateTime.Now.Date.AddDays(-2), merchant3), 24); + salesConfig.Add((DateTime.Now.Date.AddDays(-3), merchant3), 19); + + Dictionary totalCountsByOperator = new(); + Dictionary authorisedCountsByOperator = new(); + Dictionary declinedCountsByOperator = new(); + + foreach (DateTime transactionDate in transactionDates) + { + + foreach (TransactionProcessor.Database.Entities.Merchant merchant in merchants) + { + + // Build merchant sales + KeyValuePair<(DateTime, TransactionProcessor.Database.Entities.Merchant), Int32> config = salesConfig.SingleOrDefault(c => c.Key == (transactionDate, merchant)); + + for (int i = 0; i < config.Value; i++) + { + string responseCode = i switch + { + var n when n % 4 == 2 => "1009", // change value on every 4rd iteration + _ => "0000" + }; + if (!totalCountsByOperator.ContainsKey(safaricomContract.operatorId)) + { + totalCountsByOperator.Add(safaricomContract.operatorId, 0); + authorisedCountsByOperator.Add(safaricomContract.operatorId, 0); + declinedCountsByOperator.Add(safaricomContract.operatorId, 0); + } + + if (!totalCountsByOperator.ContainsKey(voucherContract.operatorId)) + { + totalCountsByOperator.Add(voucherContract.operatorId, 0); + authorisedCountsByOperator.Add(voucherContract.operatorId, 0); + declinedCountsByOperator.Add(voucherContract.operatorId, 0); + } + + Transaction transaction = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, + safaricomContract.operatorId, safaricomContract.contractId, safaricomProduct.productId, responseCode, safaricomProduct.productValue); + transactions.Add(transaction); + + totalCountsByOperator[safaricomContract.operatorId]++; + if (responseCode == "0000") + { + authorisedCountsByOperator[safaricomContract.operatorId]++; + } + else + { + declinedCountsByOperator[safaricomContract.operatorId]++; + } + + Transaction transaction1 = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, + voucherContract.operatorId, voucherContract.contractId, voucherProduct.productId, responseCode, voucherProduct.productValue); + transactions.Add(transaction1); + + totalCountsByOperator[voucherContract.operatorId]++; + if (responseCode == "0000") + { + authorisedCountsByOperator[voucherContract.operatorId]++; + } + else + { + declinedCountsByOperator[voucherContract.operatorId]++; + } + + } + } + } + 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); + + var result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/transactionsummarybyoperatorreport", payload, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + DataTransferObjects.TransactionSummaryByOperatorResponse transactionSummaryByOperatorResponse = result.Data; + + transactionSummaryByOperatorResponse.ShouldNotBeNull(); + transactionSummaryByOperatorResponse.Summary.ShouldNotBeNull(); + transactionSummaryByOperatorResponse.Summary.TotalOperators.ShouldBe(2); + transactionSummaryByOperatorResponse.Summary.TotalCount.ShouldBe(transactions.Count); + transactionSummaryByOperatorResponse.Summary.TotalValue.ShouldBe(transactions.Sum(t => t.TransactionAmount)); + + transactionSummaryByOperatorResponse.Operators.Count.ShouldBe(2); + var operators = new List { safaricomContract.operatorId, voucherContract.operatorId }; + foreach (var op in operators) + { + var row = transactionSummaryByOperatorResponse.Operators.SingleOrDefault(o => o.OperatorId == op); + row.ShouldNotBeNull(); + row.OperatorId.ShouldBe(op); + row.TotalValue.ShouldBe(transactions.Where(t => t.OperatorId == op).Sum(t => t.TransactionAmount)); + row.TotalCount.ShouldBe(transactions.Count(t => t.OperatorId == op)); + row.AuthorisedCount.ShouldBe(authorisedCountsByOperator[op]); + row.DeclinedCount.ShouldBe(declinedCountsByOperator[op]); + } + } + } } diff --git a/EstateReportingAPI.Models/MerchantDetail.cs b/EstateReportingAPI.Models/MerchantDetail.cs new file mode 100644 index 0000000..96447d8 --- /dev/null +++ b/EstateReportingAPI.Models/MerchantDetail.cs @@ -0,0 +1,27 @@ +namespace EstateReportingAPI.Models; + +public class MerchantDetail +{ + public Guid MerchantId { get; set; } + public Int32 MerchantReportingId { get; set; } + public string MerchantName { get; set; } + public Int32 TotalCount { get; set; } + public Decimal TotalValue { get; set; } + public Decimal AverageValue { get; set; } + public Int32 AuthorisedCount { get; set; } + public Int32 DeclinedCount { get; set; } + public Decimal AuthorisedPercentage { get; set; } +} + +public class OperatorDetail +{ + public Guid OperatorId { get; set; } + public Int32 OperatorReportingId { get; set; } + public string OperatorName { get; set; } + public Int32 TotalCount { get; set; } + public Decimal TotalValue { get; set; } + public Decimal AverageValue { get; set; } + public Int32 AuthorisedCount { get; set; } + public Int32 DeclinedCount { get; set; } + public Decimal AuthorisedPercentage { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/MerchantDetailSummary.cs b/EstateReportingAPI.Models/MerchantDetailSummary.cs new file mode 100644 index 0000000..690cd1e --- /dev/null +++ b/EstateReportingAPI.Models/MerchantDetailSummary.cs @@ -0,0 +1,9 @@ +namespace EstateReportingAPI.Models; + +public class MerchantDetailSummary +{ + public Int32 TotalMerchants { get; set; } + public Int32 TotalCount { get; set; } + public Decimal TotalValue { get; set; } + public Decimal AverageValue { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/OperatorDetailSummary.cs b/EstateReportingAPI.Models/OperatorDetailSummary.cs new file mode 100644 index 0000000..d36d002 --- /dev/null +++ b/EstateReportingAPI.Models/OperatorDetailSummary.cs @@ -0,0 +1,9 @@ +namespace EstateReportingAPI.Models; + +public class OperatorDetailSummary +{ + public Int32 TotalOperators { get; set; } + public Int32 TotalCount { get; set; } + public Decimal TotalValue { get; set; } + public Decimal AverageValue { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionDetail.cs b/EstateReportingAPI.Models/TransactionDetail.cs new file mode 100644 index 0000000..0157b74 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionDetail.cs @@ -0,0 +1,20 @@ +namespace EstateReportingAPI.Models; + +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.Models/TransactionDetailReportRequest.cs b/EstateReportingAPI.Models/TransactionDetailReportRequest.cs index 95b678c..1c2a70c 100644 --- a/EstateReportingAPI.Models/TransactionDetailReportRequest.cs +++ b/EstateReportingAPI.Models/TransactionDetailReportRequest.cs @@ -7,69 +7,4 @@ public class TransactionDetailReportRequest 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; } -} - -public class TransactionSummaryByMerchantRequest -{ - public List? Operators { get; set; } - public List? Merchants { get; set; } - public DateTime StartDate { get; set; } - public DateTime EndDate { get; set; } -} - -public class TransactionSummaryByMerchantResponse -{ - public List Merchants { get; set; } - public MerchantDetailSummary Summary { get; set; } -} - -public class MerchantDetail -{ - public Guid MerchantId { get; set; } - public Int32 MerchantReportingId { get; set; } - public string MerchantName { get; set; } - public Int32 TotalCount { get; set; } - public Decimal TotalValue { get; set; } - public Decimal AverageValue { get; set; } - public Int32 AuthorisedCount { get; set; } - public Int32 DeclinedCount { get; set; } - public Decimal AuthorisedPercentage { get; set; } -} - -public class MerchantDetailSummary -{ - public Int32 TotalMerchants { get; set; } - public Int32 TotalCount { get; set; } - public Decimal TotalValue { get; set; } - public Decimal AverageValue { get; set; } } \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionDetailReportResponse.cs b/EstateReportingAPI.Models/TransactionDetailReportResponse.cs new file mode 100644 index 0000000..ec1aed7 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionDetailReportResponse.cs @@ -0,0 +1,6 @@ +namespace EstateReportingAPI.Models; + +public class TransactionDetailReportResponse { + public List Transactions { get; set; } + public TransactionDetailSummary Summary { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionDetailSummary.cs b/EstateReportingAPI.Models/TransactionDetailSummary.cs new file mode 100644 index 0000000..eabc043 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionDetailSummary.cs @@ -0,0 +1,7 @@ +namespace EstateReportingAPI.Models; + +public class TransactionDetailSummary { + public Decimal TotalValue { get; set; } + public Decimal TotalFees { get; set; } + public Int32 TransactionCount { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionSummaryByMerchantRequest.cs b/EstateReportingAPI.Models/TransactionSummaryByMerchantRequest.cs new file mode 100644 index 0000000..2494926 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionSummaryByMerchantRequest.cs @@ -0,0 +1,9 @@ +namespace EstateReportingAPI.Models; + +public class TransactionSummaryByMerchantRequest +{ + public List? Operators { get; set; } + public List? Merchants { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionSummaryByMerchantResponse.cs b/EstateReportingAPI.Models/TransactionSummaryByMerchantResponse.cs new file mode 100644 index 0000000..bec4b76 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionSummaryByMerchantResponse.cs @@ -0,0 +1,7 @@ +namespace EstateReportingAPI.Models; + +public class TransactionSummaryByMerchantResponse +{ + public List Merchants { get; set; } + public MerchantDetailSummary Summary { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionSummaryByOperatorRequest.cs b/EstateReportingAPI.Models/TransactionSummaryByOperatorRequest.cs new file mode 100644 index 0000000..23d88f2 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionSummaryByOperatorRequest.cs @@ -0,0 +1,9 @@ +namespace EstateReportingAPI.Models; + +public class TransactionSummaryByOperatorRequest +{ + public List? Operators { get; set; } + public List? Merchants { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs b/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs new file mode 100644 index 0000000..be160c4 --- /dev/null +++ b/EstateReportingAPI.Models/TransactionSummaryByOperatorResponse.cs @@ -0,0 +1,7 @@ +namespace EstateReportingAPI.Models; + +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 a5259b1..616a99d 100644 --- a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs +++ b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs @@ -23,6 +23,7 @@ public MediatorRegistry() { this.AddSingleton>, TransactionRequestHandler>(); this.AddSingleton>, TransactionRequestHandler>(); this.AddSingleton>, TransactionRequestHandler>(); + this.AddSingleton>, TransactionRequestHandler>(); this.AddSingleton>>, CalendarRequestHandler>(); this.AddSingleton>>, CalendarRequestHandler>(); diff --git a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs index ba9c418..5bb3ea2 100644 --- a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs +++ b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs @@ -26,5 +26,7 @@ public static void MapTransactionEndpoints(this IEndpointRouteBuilder app) .WithStandardProduces(); group.MapPost("transactionsummarybymerchantreport", TransactionHandler.TransactionSummaryByMerchantReport) .WithStandardProduces(); + group.MapPost("transactionsummarybyoperatorreport", TransactionHandler.TransactionSummaryByOperatorReport) + .WithStandardProduces(); } } \ No newline at end of file diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index 5908442..5521543 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -113,4 +113,38 @@ TransactionSummaryByMerchantResponse SuccessFactory (Models.TransactionSummaryBy }; return ResponseFactory.FromResult(result, SuccessFactory); } + + public static async Task TransactionSummaryByOperatorReport([FromHeader] Guid estateId, + [FromBody] TransactionSummaryByOperatorRequest request, + IMediator mediator, CancellationToken cancellationToken) + { + Models.TransactionSummaryByOperatorRequest queryRequest = new() + { + Merchants = request.Merchants, + Operators = request.Operators, + StartDate = request.StartDate, + EndDate = request.EndDate + }; + var query = new TransactionQueries.TransactionSummaryByOperatorQuery(estateId, queryRequest); + var result = await mediator.Send(query, cancellationToken); + TransactionSummaryByOperatorResponse SuccessFactory(Models.TransactionSummaryByOperatorResponse r) => + new TransactionSummaryByOperatorResponse + { + Summary = new OperatorDetailSummary { TotalOperators = r.Summary.TotalOperators, TotalCount = r.Summary.TotalCount, TotalValue = r.Summary.TotalValue, AverageValue = r.Summary.AverageValue }, + Operators = r.Operators.Select(o => new OperatorDetail + { + OperatorId = o.OperatorId, + OperatorName = o.OperatorName, + OperatorReportingId = o.OperatorReportingId, + TotalCount = o.TotalCount, + TotalValue = o.TotalValue, + AverageValue = o.AverageValue, + AuthorisedCount = o.AuthorisedCount, + DeclinedCount = o.DeclinedCount, + AuthorisedPercentage = o.AuthorisedPercentage + }) + .ToList() + }; + return ResponseFactory.FromResult(result, SuccessFactory); + } } \ No newline at end of file