diff --git a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs index d8a9a96..17127e7 100644 --- a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs +++ b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs @@ -12,5 +12,6 @@ public record TodaysFailedSales(Guid EstateId, DateTime ComparisonDate, String R public record TransactionDetailReportQuery(Guid EstateId, TransactionDetailReportRequest Request) : IRequest>; public record TransactionSummaryByMerchantQuery(Guid EstateId, TransactionSummaryByMerchantRequest Request) : IRequest>; public record TransactionSummaryByOperatorQuery(Guid EstateId, TransactionSummaryByOperatorRequest Request) : IRequest>; + public record ProductPerformanceQuery(Guid EstateId, DateTime StartDate, DateTime EndDate) : IRequest>; } \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 5c37e00..c985a09 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -1,6 +1,7 @@ using EstateReportingAPI.BusinessLogic.Queries; using SimpleResults; using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.DynamicLinq; using TransactionProcessor.Database.Contexts; using TransactionProcessor.Database.Entities; using TransactionProcessor.Database.Entities.Summary; @@ -46,9 +47,10 @@ Task> GetTransactionDetailReport(Transac CancellationToken cancellationToken); Task> GetTransactionSummaryByMerchantReport(TransactionQueries.TransactionSummaryByMerchantQuery request, CancellationToken cancellationToken); - Task> GetTransactionSummaryByOperatorReport(TransactionQueries.TransactionSummaryByOperatorQuery request, CancellationToken cancellationToken); + Task> GetProductPerformanceReport(TransactionQueries.ProductPerformanceQuery request, + CancellationToken cancellationToken); #endregion } @@ -69,6 +71,20 @@ public ReportingManager(IDbContextResolver resolver) { #region Methods + 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) + { + 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 @@ -786,7 +802,6 @@ from msf in msfJoin.DefaultIfEmpty() 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 @@ -1346,6 +1361,94 @@ public async Task>> GetMerchantDevices(MerchantQueri return Result.Success(result); } + public async Task> GetProductPerformanceReport(TransactionQueries.ProductPerformanceQuery request, + 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 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 + { + cp.ProductName, + cp.ContractProductId, + cp.ContractProductReportingId, + c.ContractId, + c.ContractReportingId + } + into g + select new + { + g.Key.ProductName, + g.Key.ContractProductId, + g.Key.ContractProductReportingId, + g.Key.ContractId, + g.Key.ContractReportingId, + TransactionCount = g.Count(), + TotalAmount = g.Sum(x => x.TransactionAmount), + PercentOfTotalAmount = + grandTotalAmount == 0 + ? 0 + : 100.0m * g.Sum(x => x.TransactionAmount) / grandTotalAmount + }; + + var queryResult = await ExecuteQuerySafeToList(query, cancellationToken); + if (queryResult.IsFailed) + return ResultHelpers.CreateFailure(queryResult); + var queryResults = queryResult.Data; + if (queryResults.Any() == false) + return Result.Success(new ProductPerformanceResponse() + { + Summary = new ProductPerformanceSummary(), + ProductDetails = new List() + }); + + ProductPerformanceResponse response = new ProductPerformanceResponse() + { + ProductDetails = queryResults.Select(q => new ProductPerformanceDetail + { + ProductName = q.ProductName, + ProductId = q.ContractProductId, + ProductReportingId = q.ContractProductReportingId, + ContractId = q.ContractId, + ContractReportingId = q.ContractReportingId, + TransactionCount = q.TransactionCount, + 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() + } + }; + + 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; } diff --git a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs index 8771073..2041c05 100644 --- a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs +++ b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs @@ -9,7 +9,8 @@ public class TransactionRequestHandler : IRequestHandler>, IRequestHandler>, IRequestHandler>, - IRequestHandler> + IRequestHandler>, + IRequestHandler> { private readonly IReportingManager Manager; @@ -43,4 +44,9 @@ public async Task> Handle(Transacti { return await this.Manager.GetTransactionSummaryByOperatorReport(request, cancellationToken); } + + public async Task> Handle(TransactionQueries.ProductPerformanceQuery request, + CancellationToken cancellationToken) { + return await this.Manager.GetProductPerformanceReport(request, cancellationToken); + } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/MerchantDetail.cs b/EstateReportingAPI.DataTrasferObjects/MerchantDetail.cs new file mode 100644 index 0000000..6acdf9f --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/MerchantDetail.cs @@ -0,0 +1,25 @@ +using System; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +public class MerchantDetail { + [JsonProperty("merchant_id")] + public Guid MerchantId { get; set; } + [JsonProperty("merchant_reporting_id")] + public Int32 MerchantReportingId { get; set; } + [JsonProperty("merchant_name")] + public string MerchantName { 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; } +} \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/MerchantDetailSummary.cs b/EstateReportingAPI.DataTrasferObjects/MerchantDetailSummary.cs new file mode 100644 index 0000000..201da06 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/MerchantDetailSummary.cs @@ -0,0 +1,15 @@ +using System; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +public class MerchantDetailSummary { + [JsonProperty("total_merchants")] + public Int32 TotalMerchants { 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/OperatorDetail.cs b/EstateReportingAPI.DataTrasferObjects/OperatorDetail.cs new file mode 100644 index 0000000..9e2f0e2 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/OperatorDetail.cs @@ -0,0 +1,26 @@ +using System; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +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; } +} \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/OperatorDetailSummary.cs b/EstateReportingAPI.DataTrasferObjects/OperatorDetailSummary.cs new file mode 100644 index 0000000..f5bca16 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/OperatorDetailSummary.cs @@ -0,0 +1,16 @@ +using System; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +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/TransactionDetailReportResponse.cs b/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs index aeca40f..4a310dd 100644 --- a/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs +++ b/EstateReportingAPI.DataTrasferObjects/TransactionDetailReportResponse.cs @@ -12,85 +12,39 @@ public class TransactionDetailReportResponse public TransactionDetailSummary Summary { get; set; } } -public class TransactionSummaryByMerchantResponse -{ - [JsonProperty("merchants")] - public List Merchants { get; set; } +public class ProductPerformanceResponse { + [JsonProperty("product_details")] + public List ProductDetails { get; set; } [JsonProperty("summary")] - public MerchantDetailSummary Summary { get; set; } -} - -public class MerchantDetail { - [JsonProperty("merchant_id")] - public Guid MerchantId { get; set; } - [JsonProperty("merchant_reporting_id")] - public Int32 MerchantReportingId { get; set; } - [JsonProperty("merchant_name")] - public string MerchantName { 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 ProductPerformanceSummary Summary { get; set; } } -public class MerchantDetailSummary { - [JsonProperty("total_merchants")] - public Int32 TotalMerchants { 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; } +public class ProductPerformanceDetail { + [JsonProperty("product_name")] + public String ProductName { get; set; } + [JsonProperty("product_id")] + public Guid ProductId { get; set; } + [JsonProperty("product_reporting_id")] + public Int32 ProductReportingId { get; set; } + [JsonProperty("contract_id")] + public Guid ContractId { get; set; } + [JsonProperty("contract_reporting_id")] + public Int32 ContractReportingId { get; set; } + [JsonProperty("transaction_count")] + public Int32 TransactionCount { get; set; } + [JsonProperty("transaction_value")] + public Decimal TransactionValue { get; set; } + [JsonProperty("percentage_of_total")] + public Decimal PercentageOfTotal { 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; } +public class ProductPerformanceSummary { + [JsonProperty("total_products")] + public Int32 TotalProducts { 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("average_per_product")] + public Decimal AveragePerProduct { get; set; } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantResponse.cs b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantResponse.cs new file mode 100644 index 0000000..e4dd920 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +public class TransactionSummaryByMerchantResponse +{ + [JsonProperty("merchants")] + public List Merchants { get; set; } + [JsonProperty("summary")] + public MerchantDetailSummary Summary { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorResponse.cs b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorResponse.cs new file mode 100644 index 0000000..338e138 --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace EstateReportingAPI.DataTransferObjects; + +public class TransactionSummaryByOperatorResponse +{ + [JsonProperty("operators")] + public List Operators { get; set; } + [JsonProperty("summary")] + public OperatorDetailSummary Summary { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs b/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs index 5c7bd92..c75cddf 100644 --- a/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs +++ b/EstateReportingAPI.IntegrationTests/CalendarEndpointTests.cs @@ -3,13 +3,11 @@ using EstateReportingAPI.Models; using Io.Cucumber.Messages.Types; using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using Shouldly; using SimpleResults; using Swashbuckle.AspNetCore.SwaggerGen; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.Contracts; using System.Linq; using System.Net.Http; @@ -17,21 +15,7 @@ using System.Threading; using System.Threading.Tasks; using TransactionProcessor.Database.Contexts; -using TransactionProcessor.Database.Entities; using Xunit; -using Xunit.Abstractions; -using Contract = EstateReportingAPI.DataTransferObjects.Contract; -using Estate = EstateReportingAPI.DataTransferObjects.Estate; -using EstateOperator = EstateReportingAPI.DataTransferObjects.EstateOperator; -using Merchant = EstateReportingAPI.DataTransferObjects.Merchant; -using MerchantContract = EstateReportingAPI.DataTransferObjects.MerchantContract; -using MerchantDevice = EstateReportingAPI.DataTransferObjects.MerchantDevice; -using MerchantKpi = EstateReportingAPI.DataTransferObjects.MerchantKpi; -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; using TransactionSummaryByMerchantResponse = EstateReportingAPI.Models.TransactionSummaryByMerchantResponse; namespace EstateReportingAPI.IntegrationTests { @@ -69,1251 +53,6 @@ protected override async Task SetupStandingData() { } } - - public class ContractEndPointTests : ControllerTestsBase { - private String BaseRoute = "api/contracts"; - - [Fact] - public async Task ContractEndpoint_GetRecentContracts_ContractsReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - - 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"); - - // 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 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 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 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 helper.AddContractWithProducts("Test Estate", "PataPawa PrePay Contract", "PataPawa PrePay", prePayProductList); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/recent", CancellationToken.None); - - result.IsSuccess.ShouldBeTrue(); - - List contracts = result.Data; - contracts.Count.ShouldBe(3); - contracts.SingleOrDefault(c => c.Description == "Safaricom Contract").ShouldNotBeNull(); - contracts.SingleOrDefault(c => c.Description == "PataPawa PostPay Contract").ShouldNotBeNull(); - contracts.SingleOrDefault(c => c.Description == "PataPawa PrePay Contract").ShouldNotBeNull(); - - } - - [Fact] - public async Task ContractEndpoint_GetContracts_ContractsReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - - 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"); - - // 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 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 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 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 helper.AddContractWithProducts("Test Estate", "PataPawa PrePay Contract", "PataPawa PrePay", prePayProductList); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}", CancellationToken.None); - - result.IsSuccess.ShouldBeTrue(); - - List contracts = result.Data; - contracts.Count.ShouldBe(4); - contracts.SingleOrDefault(c => c.Description == "Safaricom Contract").ShouldNotBeNull(); - contracts.SingleOrDefault(c => c.Description == "Healthcare Centre 1 Contract").ShouldNotBeNull(); - contracts.SingleOrDefault(c => c.Description == "PataPawa PostPay Contract").ShouldNotBeNull(); - contracts.SingleOrDefault(c => c.Description == "PataPawa PrePay Contract").ShouldNotBeNull(); - - } - - [Fact] - public async Task ContractEndpoint_GetContract_ContractReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - - 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"); - - // 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 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 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 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) }; - var ppprepayContractId = await helper.AddContractWithProducts("Test Estate", "PataPawa PrePay Contract", "PataPawa PrePay", prePayProductList); - - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/{ppprepayContractId}", CancellationToken.None); - - result.IsSuccess.ShouldBeTrue(); - - Contract contract = result.Data; - contract.ShouldNotBeNull(); - contract.Description.ShouldBe("PataPawa PrePay Contract"); - - } - - protected override async Task ClearStandingData() { - - } - - protected override async Task SetupStandingData() { - - } - } - - public class EstateEndpointTests : ControllerTestsBase { - private String BaseRoute = "api/estates"; - - [Fact] - public async Task EstateEndpoint_GetEstates_EstateReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}", CancellationToken.None); - - result.IsSuccess.ShouldBeTrue(); - - Estate estate = result.Data; - estate.ShouldNotBeNull(); - estate.EstateName.ShouldBe("Test Estate"); - estate.Reference.ShouldBe("Ref1"); - } - - [Fact] - public async Task EstateEndpoint_GetEstateOperator_EstateOperatorsReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - await this.helper.AddOperator("Test Estate", "Safaricom"); - await this.helper.AddOperator("Test Estate", "Voucher"); - - await this.helper.AddEstateOperators("Test Estate", ["Safaricom", "Voucher"]); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/operators", CancellationToken.None); - - result.IsSuccess.ShouldBeTrue(); - - List estateOperators = result.Data; - estateOperators.Count.ShouldBe(2); - estateOperators.SingleOrDefault(e => e.Name == "Safaricom").ShouldNotBeNull(); - estateOperators.SingleOrDefault(e => e.Name == "Voucher").ShouldNotBeNull(); - } - - protected override async Task ClearStandingData() { - - } - - protected override async Task SetupStandingData() { - - } - } - - public class OperatorEndpointTests : ControllerTestsBase { - private String BaseRoute = "api/operators"; - - [Fact] - public async Task OperatorEndpoint_GetOperators_OperatorsReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - await this.helper.AddOperator("Test Estate", "Safaricom"); - await this.helper.AddOperator("Test Estate", "Voucher"); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - List operators = result.Data; - operators.Count.ShouldBe(2); - operators.SingleOrDefault(o => o.Name == "Safaricom").ShouldNotBeNull(); - operators.SingleOrDefault(o => o.Name == "Voucher").ShouldNotBeNull(); - } - - [Fact] - public async Task OperatorEndpoint_GetOperator_OperatorReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - var operatorId = await this.helper.AddOperator("Test Estate", "Safaricom"); - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/{operatorId}", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - Operator operatorData = result.Data; - operatorData.ShouldNotBeNull(); - operatorData.Name.ShouldBe("Safaricom"); - } - - protected override async Task ClearStandingData() { - - } - - protected override async Task SetupStandingData() { - - } - } - - public class MerchantEndpointTests : ControllerTestsBase { - private String BaseRoute = "api/merchants"; - - [Fact] - public async Task MerchantEndpoint_GetMerchants_MerchantsReturned() { - await helper.AddEstate("Test Estate", "Ref1"); - for (int i = 0; i < 10; i++) { - await helper.AddMerchant("Test Estate", $"Test Merchant {i}", DateTime.Now, DateTime.Now, - ("Address Line 1", $"Test Town {i}", $"TE57 {i}NG", $"Region {i}"), - ($"Contact {i}", @"{i}@2.com", $"{i}23456")); - } - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchants = result.Data; - - merchants.ShouldNotBeNull(); - merchants.Count.ShouldBe(10); - - for (int i = 0; i < 10; i++) { - Merchant? expected = merchants.SingleOrDefault(m => m.Name == $"Test Merchant {i}"); - expected.ShouldNotBeNull(); - } - } - - [Fact] - public async Task MerchantEndpoint_GetMerchant_MerchantReturned() - { - await helper.AddEstate("Test Estate", "Ref1"); - var merchantId = await helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456")); - - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/{merchantId}", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchant = result.Data; - - merchant.ShouldNotBeNull(); - merchant.Name.ShouldBe("Test Merchant 1"); - } - - [Fact] - public async Task MerchantEndpoint_GetRecentMerchants_MerchantsReturned() - { - await helper.AddEstate("Test Estate", "Ref1"); - for (int i = 0; i < 10; i++) - { - await helper.AddMerchant("Test Estate", $"Test Merchant {i}", DateTime.Now.AddDays(i*-1), DateTime.Now, - ("Address Line 1", $"Test Town {i}", $"TE57 {i}NG", $"Region {i}"), - ($"Contact {i}", @"{i}@2.com", $"{i}23456")); - } - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/recent", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchants = result.Data; - - merchants.ShouldNotBeNull(); - merchants.Count.ShouldBe(3); - merchants.SingleOrDefault(m => m.Name == "Test Merchant 0").ShouldNotBeNull(); - merchants.SingleOrDefault(m => m.Name == "Test Merchant 1").ShouldNotBeNull(); - merchants.SingleOrDefault(m => m.Name == "Test Merchant 2").ShouldNotBeNull(); - } - - - [Fact] - public async Task MerchantEndpoint_GetMerchantOperators_MerchantOperatorsReturned() - { - await helper.AddEstate("Test Estate", "Ref1"); - - await this.helper.AddOperator("Test Estate", "Safaricom"); - await this.helper.AddOperator("Test Estate", "Voucher"); - - var merchantId = await helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), operators: ["Safaricom", "Voucher"]); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/{merchantId}/operators", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchantOperators = result.Data; - - merchantOperators.ShouldNotBeNull(); - merchantOperators.Count.ShouldBe(2); - merchantOperators.SingleOrDefault(m => m.OperatorName == "Safaricom").ShouldNotBeNull(); - merchantOperators.SingleOrDefault(m => m.OperatorName == "Voucher").ShouldNotBeNull(); - } - - [Fact] - public async Task MerchantEndpoint_GetMerchantContracts_MerchantContractsReturned() - { - await helper.AddEstate("Test Estate", "Ref1"); - - await this.helper.AddOperator("Test Estate", "Safaricom"); - await this.helper.AddOperator("Test Estate", "Voucher"); - - 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) }; - await helper.AddContractWithProducts("Test Estate", "Healthcare Centre 1 Contract", "Voucher", voucherProductList); - - var merchantId = await helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), operators: ["Safaricom", "Voucher"], - ["Safaricom Contract", "Healthcare Centre 1 Contract"]); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/{merchantId}/contracts", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchantContracts = result.Data; - - merchantContracts.ShouldNotBeNull(); - merchantContracts.Count.ShouldBe(2); - merchantContracts.SingleOrDefault(m => m.ContractName == "Safaricom Contract").ShouldNotBeNull(); - merchantContracts.SingleOrDefault(m => m.ContractName == "Healthcare Centre 1 Contract").ShouldNotBeNull(); - } - - [Fact] - public async Task MerchantEndpoint_GetMerchantDevices_MerchantDevicesReturned() - { - await helper.AddEstate("Test Estate", "Ref1"); - - var merchantId = await helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - - Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/{merchantId}/devices", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchantDevices = result.Data; - - merchantDevices.ShouldNotBeNull(); - merchantDevices.Count.ShouldBe(1); - merchantDevices.SingleOrDefault(m => m.DeviceIdentifier == "123456").ShouldNotBeNull(); - } - - [Fact] - public async Task MerchantEndpoint_GetMerchantKpis_MerchantKpisReturned() - { - await helper.AddEstate("Test Estate", "Ref1"); - - await helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 2", DateTime.Now, DateTime.Now.AddMinutes(-10), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 3", DateTime.Now, DateTime.Now.AddHours(-2), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 4", DateTime.Now, DateTime.Now.AddHours(-3), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 5", DateTime.Now, DateTime.Now.AddDays(-2), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 6", DateTime.Now, DateTime.Now.AddDays(-1), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 7", DateTime.Now, DateTime.Now.AddDays(-3), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - await helper.AddMerchant("Test Estate", $"Test Merchant 8", DateTime.Now, DateTime.Now.AddDays(-10), - ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), - ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); - - Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/kpis", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var merchantKpis = result.Data; - - merchantKpis.MerchantsWithSaleInLastHour.ShouldBe(2); - merchantKpis.MerchantsWithNoSaleToday.ShouldBe(3); - merchantKpis.MerchantsWithNoSaleInLast7Days.ShouldBe(1); - } - - - protected override async Task ClearStandingData() { - - } - - protected override async Task SetupStandingData() { - - } - } - - public class TransactionsEndpointTests : ControllerTestsBase { - private String BaseRoute = "api/transactions"; - - public TransactionsEndpointTests(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 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 helper.AddMerchant("Test Estate", "Test Merchant 1", DateTime.MinValue, DateTime.MinValue, default, default); - await helper.AddMerchant("Test Estate", "Test Merchant 2", DateTime.MinValue, DateTime.MinValue, default, default); - await helper.AddMerchant("Test Estate", "Test Merchant 3", DateTime.MinValue, DateTime.MinValue, default, default); - await 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 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 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 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 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 helper.AddResponseCode(0, "Success"); - await helper.AddResponseCode(1000, "Unknown Device"); - await helper.AddResponseCode(1001, "Unknown Estate"); - await helper.AddResponseCode(1002, "Unknown Merchant"); - await helper.AddResponseCode(1003, "No Devices Configured"); - - sw.Stop(); - this.TestOutputHelper.WriteLine($"Setup Response Codes {sw.ElapsedMilliseconds}ms"); - sw.Restart(); - - 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.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, 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 TransactionsEndpoint_TodaysSales_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}", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - var todaysSales = result.Data; - - todaysSales.ShouldNotBeNull(); - todaysSales.ComparisonSalesCount.ShouldBe(comparisonDateTransactions.Count); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); - - todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); - } - - - [Fact] - 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, 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)); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.OperatorId == operatorId).Sum(c => c.TransactionAmount)); - - todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count(c => c.OperatorId == operatorId)); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.OperatorId == operatorId).Sum(c => c.TransactionAmount)); - } - - - [Fact] - 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)); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.MerchantId == merchantId).Sum(c => c.TransactionAmount)); - - todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count(c => c.MerchantId == merchantId)); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.MerchantId == merchantId).Sum(c => c.TransactionAmount)); - } - - [Fact] - public async Task TransactionsEndpoint_TodaysFailedSales_SalesReturned() { - - //EstateManagementContext context = new EstateManagementContext(GetLocalConnectionString($"TransactionProcessorReadModel-{TestId.ToString()}")); - var todaysTransactions = new List(); - var comparisonDateTransactions = new List(); - //DatabaseHelper helper1 = new DatabaseHelper(context); - // TODO: make counts dynamic - DateTime todaysDateTime = DateTime.Now; - //todaysDateTime = todaysDateTime.AddHours(12).AddMinutes(30); - - Dictionary transactionCounts = new() { { "Test Merchant 1", 3 }, { "Test Merchant 2", 6 }, { "Test Merchant 3", 2 }, { "Test Merchant 4", 0 } }; - - DateTime comparisonDate = todaysDateTime.AddDays(-1); - - 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, "1009", product.productValue); - todaysTransactions.Add(transaction); - } - } - } - } - - await 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.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "1009", product.productValue); - comparisonDateTransactions.Add(transaction); - } - } - } - } - - await 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}/todaysfailedsales?comparisonDate={comparisonDate:yyyy-MM-dd}&responseCode=1009", CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - DataTransferObjects.TodaysSales? todaysSales = result.Data; - - todaysSales.ShouldNotBeNull(); - todaysSales.ComparisonSalesCount.ShouldBe(comparisonDateTransactions.Count); - todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); - - 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()); - } - } - - - [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"); - 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 totalCountsByMerchant = new(); - Dictionary authorisedCountsByMerchant = new(); - Dictionary declinedCountsByMerchant = 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 (!totalCountsByMerchant.ContainsKey(merchant)) - { - totalCountsByMerchant.Add(merchant, 0); - authorisedCountsByMerchant.Add(merchant, 0); - declinedCountsByMerchant.Add(merchant, 0); - } - - 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]++; - } - - Transaction transaction1 = await helper.BuildTransactionX(transactionDate, merchant.MerchantId, - voucherContract.operatorId, voucherContract.contractId, voucherProduct.productId, responseCode, voucherProduct.productValue); - transactions.Add(transaction1); - - totalCountsByMerchant[merchant]++; - if (responseCode == "0000") - { - authorisedCountsByMerchant[merchant]++; - } - else - { - declinedCountsByMerchant[merchant]++; - } - } - } - } - 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}/transactionsummarybymerchantreport", payload, CancellationToken.None); - result.IsSuccess.ShouldBeTrue(); - DataTransferObjects.TransactionSummaryByMerchantResponse transactionSummaryByMerchantResponse = result.Data; - - transactionSummaryByMerchantResponse.ShouldNotBeNull(); - transactionSummaryByMerchantResponse.Summary.ShouldNotBeNull(); - transactionSummaryByMerchantResponse.Summary.TotalMerchants.ShouldBe(3); - transactionSummaryByMerchantResponse.Summary.TotalCount.ShouldBe(transactions.Count); - transactionSummaryByMerchantResponse.Summary.TotalValue.ShouldBe(transactions.Sum(t => t.TransactionAmount)); - - transactionSummaryByMerchantResponse.Merchants.Count.ShouldBe(3); - 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); - row.TotalValue.ShouldBe(transactions.Where(t => t.MerchantId == mr.MerchantId).Sum(t => t.TransactionAmount)); - row.TotalCount.ShouldBe(transactions.Count(t => t.MerchantId == mr.MerchantId)); - row.AuthorisedCount.ShouldBe(authorisedCountsByMerchant[mr]); - row.DeclinedCount.ShouldBe(declinedCountsByMerchant[mr]); - } - } - - [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.IntegrationTests/ContractEndPointTests.cs b/EstateReportingAPI.IntegrationTests/ContractEndPointTests.cs new file mode 100644 index 0000000..9ce619a --- /dev/null +++ b/EstateReportingAPI.IntegrationTests/ContractEndPointTests.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EstateReportingAPI.DataTransferObjects; +using Shouldly; +using SimpleResults; +using Xunit; + +namespace EstateReportingAPI.IntegrationTests; + +public class ContractEndPointTests : ControllerTestsBase { + private String BaseRoute = "api/contracts"; + + [Fact] + public async Task ContractEndpoint_GetRecentContracts_ContractsReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + + 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"); + + // 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); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/recent", CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + + List contracts = result.Data; + contracts.Count.ShouldBe(3); + contracts.SingleOrDefault(c => c.Description == "Safaricom Contract").ShouldNotBeNull(); + contracts.SingleOrDefault(c => c.Description == "PataPawa PostPay Contract").ShouldNotBeNull(); + contracts.SingleOrDefault(c => c.Description == "PataPawa PrePay Contract").ShouldNotBeNull(); + + } + + [Fact] + public async Task ContractEndpoint_GetContracts_ContractsReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + + 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"); + + // 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); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}", CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + + List contracts = result.Data; + contracts.Count.ShouldBe(4); + contracts.SingleOrDefault(c => c.Description == "Safaricom Contract").ShouldNotBeNull(); + contracts.SingleOrDefault(c => c.Description == "Healthcare Centre 1 Contract").ShouldNotBeNull(); + contracts.SingleOrDefault(c => c.Description == "PataPawa PostPay Contract").ShouldNotBeNull(); + contracts.SingleOrDefault(c => c.Description == "PataPawa PrePay Contract").ShouldNotBeNull(); + + } + + [Fact] + public async Task ContractEndpoint_GetContract_ContractReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + + 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"); + + // 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) }; + var ppprepayContractId = await this.helper.AddContractWithProducts("Test Estate", "PataPawa PrePay Contract", "PataPawa PrePay", prePayProductList); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/{ppprepayContractId}", CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + + Contract contract = result.Data; + contract.ShouldNotBeNull(); + contract.Description.ShouldBe("PataPawa PrePay Contract"); + + } + + protected override async Task ClearStandingData() { + + } + + protected override async Task SetupStandingData() { + + } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/EstateEndpointTests.cs b/EstateReportingAPI.IntegrationTests/EstateEndpointTests.cs new file mode 100644 index 0000000..49ddf24 --- /dev/null +++ b/EstateReportingAPI.IntegrationTests/EstateEndpointTests.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EstateReportingAPI.DataTransferObjects; +using Shouldly; +using SimpleResults; +using Xunit; + +namespace EstateReportingAPI.IntegrationTests; + +public class EstateEndpointTests : ControllerTestsBase { + private String BaseRoute = "api/estates"; + + [Fact] + public async Task EstateEndpoint_GetEstates_EstateReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}", CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + + Estate estate = result.Data; + estate.ShouldNotBeNull(); + estate.EstateName.ShouldBe("Test Estate"); + estate.Reference.ShouldBe("Ref1"); + } + + [Fact] + public async Task EstateEndpoint_GetEstateOperator_EstateOperatorsReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + await this.helper.AddOperator("Test Estate", "Safaricom"); + await this.helper.AddOperator("Test Estate", "Voucher"); + + await this.helper.AddEstateOperators("Test Estate", ["Safaricom", "Voucher"]); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/operators", CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + + List estateOperators = result.Data; + estateOperators.Count.ShouldBe(2); + estateOperators.SingleOrDefault(e => e.Name == "Safaricom").ShouldNotBeNull(); + estateOperators.SingleOrDefault(e => e.Name == "Voucher").ShouldNotBeNull(); + } + + protected override async Task ClearStandingData() { + + } + + protected override async Task SetupStandingData() { + + } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/MerchantEndpointTests.cs b/EstateReportingAPI.IntegrationTests/MerchantEndpointTests.cs new file mode 100644 index 0000000..785996a --- /dev/null +++ b/EstateReportingAPI.IntegrationTests/MerchantEndpointTests.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EstateReportingAPI.DataTransferObjects; +using Shouldly; +using SimpleResults; +using Xunit; + +namespace EstateReportingAPI.IntegrationTests; + +public class MerchantEndpointTests : ControllerTestsBase { + private String BaseRoute = "api/merchants"; + + [Fact] + public async Task MerchantEndpoint_GetMerchants_MerchantsReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + for (int i = 0; i < 10; i++) { + await this.helper.AddMerchant("Test Estate", $"Test Merchant {i}", DateTime.Now, DateTime.Now, + ("Address Line 1", $"Test Town {i}", $"TE57 {i}NG", $"Region {i}"), + ($"Contact {i}", @"{i}@2.com", $"{i}23456")); + } + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchants = result.Data; + + merchants.ShouldNotBeNull(); + merchants.Count.ShouldBe(10); + + for (int i = 0; i < 10; i++) { + Merchant? expected = merchants.SingleOrDefault(m => m.Name == $"Test Merchant {i}"); + expected.ShouldNotBeNull(); + } + } + + [Fact] + public async Task MerchantEndpoint_GetMerchant_MerchantReturned() + { + await this.helper.AddEstate("Test Estate", "Ref1"); + var merchantId = await this.helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456")); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/{merchantId}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchant = result.Data; + + merchant.ShouldNotBeNull(); + merchant.Name.ShouldBe("Test Merchant 1"); + } + + [Fact] + public async Task MerchantEndpoint_GetRecentMerchants_MerchantsReturned() + { + await this.helper.AddEstate("Test Estate", "Ref1"); + for (int i = 0; i < 10; i++) + { + await this.helper.AddMerchant("Test Estate", $"Test Merchant {i}", DateTime.Now.AddDays(i*-1), DateTime.Now, + ("Address Line 1", $"Test Town {i}", $"TE57 {i}NG", $"Region {i}"), + ($"Contact {i}", @"{i}@2.com", $"{i}23456")); + } + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/recent", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchants = result.Data; + + merchants.ShouldNotBeNull(); + merchants.Count.ShouldBe(3); + merchants.SingleOrDefault(m => m.Name == "Test Merchant 0").ShouldNotBeNull(); + merchants.SingleOrDefault(m => m.Name == "Test Merchant 1").ShouldNotBeNull(); + merchants.SingleOrDefault(m => m.Name == "Test Merchant 2").ShouldNotBeNull(); + } + + + [Fact] + public async Task MerchantEndpoint_GetMerchantOperators_MerchantOperatorsReturned() + { + await this.helper.AddEstate("Test Estate", "Ref1"); + + await this.helper.AddOperator("Test Estate", "Safaricom"); + await this.helper.AddOperator("Test Estate", "Voucher"); + + var merchantId = await this.helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), operators: ["Safaricom", "Voucher"]); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/{merchantId}/operators", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchantOperators = result.Data; + + merchantOperators.ShouldNotBeNull(); + merchantOperators.Count.ShouldBe(2); + merchantOperators.SingleOrDefault(m => m.OperatorName == "Safaricom").ShouldNotBeNull(); + merchantOperators.SingleOrDefault(m => m.OperatorName == "Voucher").ShouldNotBeNull(); + } + + [Fact] + public async Task MerchantEndpoint_GetMerchantContracts_MerchantContractsReturned() + { + await this.helper.AddEstate("Test Estate", "Ref1"); + + await this.helper.AddOperator("Test Estate", "Safaricom"); + await this.helper.AddOperator("Test Estate", "Voucher"); + + 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); + + var merchantId = await this.helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), operators: ["Safaricom", "Voucher"], + ["Safaricom Contract", "Healthcare Centre 1 Contract"]); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/{merchantId}/contracts", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchantContracts = result.Data; + + merchantContracts.ShouldNotBeNull(); + merchantContracts.Count.ShouldBe(2); + merchantContracts.SingleOrDefault(m => m.ContractName == "Safaricom Contract").ShouldNotBeNull(); + merchantContracts.SingleOrDefault(m => m.ContractName == "Healthcare Centre 1 Contract").ShouldNotBeNull(); + } + + [Fact] + public async Task MerchantEndpoint_GetMerchantDevices_MerchantDevicesReturned() + { + await this.helper.AddEstate("Test Estate", "Ref1"); + + var merchantId = await this.helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}/{merchantId}/devices", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchantDevices = result.Data; + + merchantDevices.ShouldNotBeNull(); + merchantDevices.Count.ShouldBe(1); + merchantDevices.SingleOrDefault(m => m.DeviceIdentifier == "123456").ShouldNotBeNull(); + } + + [Fact] + public async Task MerchantEndpoint_GetMerchantKpis_MerchantKpisReturned() + { + await this.helper.AddEstate("Test Estate", "Ref1"); + + await this.helper.AddMerchant("Test Estate", $"Test Merchant 1", DateTime.Now, DateTime.Now, + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 2", DateTime.Now, DateTime.Now.AddMinutes(-10), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 3", DateTime.Now, DateTime.Now.AddHours(-2), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 4", DateTime.Now, DateTime.Now.AddHours(-3), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 5", DateTime.Now, DateTime.Now.AddDays(-2), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 6", DateTime.Now, DateTime.Now.AddDays(-1), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 7", DateTime.Now, DateTime.Now.AddDays(-3), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + await this.helper.AddMerchant("Test Estate", $"Test Merchant 8", DateTime.Now, DateTime.Now.AddDays(-10), + ("Address Line 1", $"Test Town", $"TE57 1NG", $"Region"), + ("Contact 1", "1@2.com", "123456"), devices: ["123456"]); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/kpis", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var merchantKpis = result.Data; + + merchantKpis.MerchantsWithSaleInLastHour.ShouldBe(2); + merchantKpis.MerchantsWithNoSaleToday.ShouldBe(3); + merchantKpis.MerchantsWithNoSaleInLast7Days.ShouldBe(1); + } + + + protected override async Task ClearStandingData() { + + } + + protected override async Task SetupStandingData() { + + } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/OperatorEndpointTests.cs b/EstateReportingAPI.IntegrationTests/OperatorEndpointTests.cs new file mode 100644 index 0000000..d32d891 --- /dev/null +++ b/EstateReportingAPI.IntegrationTests/OperatorEndpointTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EstateReportingAPI.DataTransferObjects; +using Shouldly; +using SimpleResults; +using Xunit; + +namespace EstateReportingAPI.IntegrationTests; + +public class OperatorEndpointTests : ControllerTestsBase { + private String BaseRoute = "api/operators"; + + [Fact] + public async Task OperatorEndpoint_GetOperators_OperatorsReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + await this.helper.AddOperator("Test Estate", "Safaricom"); + await this.helper.AddOperator("Test Estate", "Voucher"); + + Result> result = await this.CreateAndSendHttpRequestMessage>($"{this.BaseRoute}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + List operators = result.Data; + operators.Count.ShouldBe(2); + operators.SingleOrDefault(o => o.Name == "Safaricom").ShouldNotBeNull(); + operators.SingleOrDefault(o => o.Name == "Voucher").ShouldNotBeNull(); + } + + [Fact] + public async Task OperatorEndpoint_GetOperator_OperatorReturned() { + await this.helper.AddEstate("Test Estate", "Ref1"); + var operatorId = await this.helper.AddOperator("Test Estate", "Safaricom"); + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/{operatorId}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + Operator operatorData = result.Data; + operatorData.ShouldNotBeNull(); + operatorData.Name.ShouldBe("Safaricom"); + } + + protected override async Task ClearStandingData() { + + } + + protected override async Task SetupStandingData() { + + } +} \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs new file mode 100644 index 0000000..e47faea --- /dev/null +++ b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs @@ -0,0 +1,944 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EstateReportingAPI.DataTransferObjects; +using Newtonsoft.Json; +using Shouldly; +using SimpleResults; +using TransactionProcessor.Database.Entities; +using Xunit; +using Xunit.Abstractions; + +namespace EstateReportingAPI.IntegrationTests; + +public class TransactionsEndpointTests : ControllerTestsBase { + private String BaseRoute = "api/transactions"; + + public TransactionsEndpointTests(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 TransactionsEndpoint_TodaysSales_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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + comparisonDateTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(comparisonDateTransactions); + + await this.helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); + await this.helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); + await this.helper.RunTodaysTransactionsSummaryProcessing(todaysDateTime.Date); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todayssales?comparisonDate={comparisonDate:yyyy-MM-dd}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var todaysSales = result.Data; + + todaysSales.ShouldNotBeNull(); + todaysSales.ComparisonSalesCount.ShouldBe(comparisonDateTransactions.Count); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); + + todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); + } + + + [Fact] + 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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + comparisonDateTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(comparisonDateTransactions); + + await this.helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); + await this.helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); + await this.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)); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.OperatorId == operatorId).Sum(c => c.TransactionAmount)); + + todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count(c => c.OperatorId == operatorId)); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.OperatorId == operatorId).Sum(c => c.TransactionAmount)); + } + + + [Fact] + 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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.helper.BuildTransactionX(comparisonDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + comparisonDateTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(comparisonDateTransactions); + + await this.helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); + await this.helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); + await this.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)); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.MerchantId == merchantId).Sum(c => c.TransactionAmount)); + + todaysSales.TodaysSalesCount.ShouldBe(todaysTransactions.Count(c => c.MerchantId == merchantId)); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Where(c => c.MerchantId == merchantId).Sum(c => c.TransactionAmount)); + } + + [Fact] + public async Task TransactionsEndpoint_TodaysFailedSales_SalesReturned() { + + //EstateManagementContext context = new EstateManagementContext(GetLocalConnectionString($"TransactionProcessorReadModel-{TestId.ToString()}")); + var todaysTransactions = new List(); + var comparisonDateTransactions = new List(); + //DatabaseHelper helper1 = new DatabaseHelper(context); + // TODO: make counts dynamic + DateTime todaysDateTime = DateTime.Now; + //todaysDateTime = todaysDateTime.AddHours(12).AddMinutes(30); + + Dictionary transactionCounts = new() { { "Test Merchant 1", 3 }, { "Test Merchant 2", 6 }, { "Test Merchant 3", 2 }, { "Test Merchant 4", 0 } }; + + DateTime comparisonDate = todaysDateTime.AddDays(-1); + + foreach (var merchant in this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.helper.BuildTransactionX(todaysDateTime.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "1009", product.productValue); + todaysTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(todaysTransactions); + + // Comparison Date sales + foreach (var merchant in this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.helper.BuildTransactionX(comparisonDate.AddHours(-1), merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "1009", product.productValue); + comparisonDateTransactions.Add(transaction); + } + } + } + } + + await this.helper.AddTransactionsX(comparisonDateTransactions); + + + await this.helper.RunTodaysTransactionsSummaryProcessing(comparisonDate.Date); + await this.helper.RunHistoricTransactionsSummaryProcessing(comparisonDate.Date); + await this.helper.RunTodaysTransactionsSummaryProcessing(todaysDateTime.Date); + + Result result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/todaysfailedsales?comparisonDate={comparisonDate:yyyy-MM-dd}&responseCode=1009", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + DataTransferObjects.TodaysSales? todaysSales = result.Data; + + todaysSales.ShouldNotBeNull(); + todaysSales.ComparisonSalesCount.ShouldBe(comparisonDateTransactions.Count); + todaysSales.ComparisonSalesValue.ShouldBe(comparisonDateTransactions.Sum(c => c.TransactionAmount)); + + 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 this.merchantsList) { + foreach (var contract in this.contractList) { + var productList = this.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 this.helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await this.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 this.merchantsList) + { + foreach (var contract in this.contractList) + { + var productList = this.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 this.helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await this.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 this.merchantsList) + { + foreach (var contract in this.contractList) + { + var productList = this.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 this.helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await this.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 this.merchantsList) + { + foreach (var contract in this.contractList) + { + var productList = this.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 this.helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + } + } + + await this.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()); + } + } + + + [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"); + 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 = this.contractProducts.Single(cp => cp.Key == safaricomContract.contractId).Value.First(); + var voucherProduct = this.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 totalCountsByMerchant = new(); + Dictionary authorisedCountsByMerchant = new(); + Dictionary declinedCountsByMerchant = 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 (!totalCountsByMerchant.ContainsKey(merchant)) + { + totalCountsByMerchant.Add(merchant, 0); + authorisedCountsByMerchant.Add(merchant, 0); + declinedCountsByMerchant.Add(merchant, 0); + } + + Transaction transaction = await this.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]++; + } + + Transaction transaction1 = await this.helper.BuildTransactionX(transactionDate, merchant.MerchantId, + voucherContract.operatorId, voucherContract.contractId, voucherProduct.productId, responseCode, voucherProduct.productValue); + transactions.Add(transaction1); + + totalCountsByMerchant[merchant]++; + if (responseCode == "0000") + { + authorisedCountsByMerchant[merchant]++; + } + else + { + declinedCountsByMerchant[merchant]++; + } + } + } + } + await this.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}/transactionsummarybymerchantreport", payload, CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + DataTransferObjects.TransactionSummaryByMerchantResponse transactionSummaryByMerchantResponse = result.Data; + + transactionSummaryByMerchantResponse.ShouldNotBeNull(); + transactionSummaryByMerchantResponse.Summary.ShouldNotBeNull(); + transactionSummaryByMerchantResponse.Summary.TotalMerchants.ShouldBe(3); + transactionSummaryByMerchantResponse.Summary.TotalCount.ShouldBe(transactions.Count); + transactionSummaryByMerchantResponse.Summary.TotalValue.ShouldBe(transactions.Sum(t => t.TransactionAmount)); + + transactionSummaryByMerchantResponse.Merchants.Count.ShouldBe(3); + 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); + row.TotalValue.ShouldBe(transactions.Where(t => t.MerchantId == mr.MerchantId).Sum(t => t.TransactionAmount)); + row.TotalCount.ShouldBe(transactions.Count(t => t.MerchantId == mr.MerchantId)); + row.AuthorisedCount.ShouldBe(authorisedCountsByMerchant[mr]); + row.DeclinedCount.ShouldBe(declinedCountsByMerchant[mr]); + } + } + + [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 = this.contractProducts.Single(cp => cp.Key == safaricomContract.contractId).Value.First(); + var voucherProduct = this.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 this.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 this.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 this.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]); + } + } + + + [Fact] + public async Task TransactionsEndpoint_ProductPerformanceReport_SummaryDataReturned() { + Dictionary<(String @operator, String product), Int32> productPerformanceData = new(); + productPerformanceData.Add(("Safaricom", "200 KES Topup"), 6); + productPerformanceData.Add(("Safaricom", "100 KES Topup"), 10); + productPerformanceData.Add(("Safaricom", "50 KES Topup"), 20); + productPerformanceData.Add(("Safaricom", "Custom"), 5); + productPerformanceData.Add(("Voucher", "10 KES Voucher"), 15); + productPerformanceData.Add(("Voucher", "Custom"), 8); + productPerformanceData.Add(("PataPawa PostPay", "Post Pay Bill Pay"), 12); + productPerformanceData.Add(("PataPawa PrePay", "Pre Pay Bill Pay"), 18); + + var transactions = new List(); + + TransactionProcessor.Database.Entities.Merchant merchant = this.merchantsList.SingleOrDefault(m => m.Name == "Test Merchant 1"); + + List transactionDates = [ + DateTime.Now.Date, + DateTime.Now.Date.AddDays(-1), + DateTime.Now.Date.AddDays(-2), + DateTime.Now.Date.AddDays(-3) + ]; + + foreach (DateTime transactionDate in transactionDates) { + foreach (var productData in productPerformanceData) { + var transactionCount = productData.Value; + var contract = this.contractList.SingleOrDefault(c => c.operatorName == productData.Key.@operator); + var productList = this.contractProducts.Where(cp => cp.Key == contract.contractId).SelectMany(cp => cp.Value).ToList(); + var product = productList.SingleOrDefault(p => p.productName == productData.Key.product); + for (int i = 0; i < transactionCount; i++) { + Transaction transaction = await this.helper.BuildTransactionX(transactionDate, merchant.MerchantId, contract.operatorId, contract.contractId, product.productId, "0000", product.productValue); + transactions.Add(transaction); + } + } + } + + await this.helper.AddTransactionsX(transactions); + + var result = await this.CreateAndSendHttpRequestMessage($"{this.BaseRoute}/productperformancereport?startDate={DateTime.Now.Date.AddDays(-3):yyyy-MM-dd}&endDate={DateTime.Now.Date:yyyy-MM-dd}", CancellationToken.None); + result.IsSuccess.ShouldBeTrue(); + var productPerformanceResponse= result.Data; + + productPerformanceResponse.ShouldNotBeNull(); + productPerformanceResponse.Summary.ShouldNotBeNull(); + productPerformanceResponse.Summary.TotalProducts.ShouldBe(8); + productPerformanceResponse.Summary.TotalCount.ShouldBe(transactions.Count); + productPerformanceResponse.Summary.TotalValue.ShouldBe(transactions.Sum(t => t.TransactionAmount)); + + foreach (KeyValuePair<(String @operator, String product), Int32> productData in productPerformanceData) { + var contract = this.contractList.SingleOrDefault(c => c.operatorName == productData.Key.@operator); + var productList = this.contractProducts.Single(cp => cp.Key == contract.contractId).Value; + var product = productList.SingleOrDefault(p => p.productName == productData.Key.product); + var productPerformanceResponseDetail = productPerformanceResponse.ProductDetails.SingleOrDefault(p => p.ProductId == product.productId); + productPerformanceResponseDetail.ShouldNotBeNull(); + productPerformanceResponseDetail.TransactionCount.ShouldBe(transactions.Count(t => t.ContractProductId == product.productId)); + productPerformanceResponseDetail.TransactionValue.ShouldBe(transactions.Where(t => t.ContractProductId == product.productId).Sum(t => t.TransactionAmount)); + } + + } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/ProductPerformanceDetail.cs b/EstateReportingAPI.Models/ProductPerformanceDetail.cs new file mode 100644 index 0000000..6dfa04c --- /dev/null +++ b/EstateReportingAPI.Models/ProductPerformanceDetail.cs @@ -0,0 +1,13 @@ +namespace EstateReportingAPI.Models; + +public class ProductPerformanceDetail +{ + public String ProductName { get; set; } + public Guid ProductId { get; set; } + public Int32 ProductReportingId { get; set; } + public Guid ContractId { get; set; } + public Int32 ContractReportingId { get; set; } + public Int32 TransactionCount { get; set; } + public Decimal TransactionValue { get; set; } + public Decimal PercentageOfTotal { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/ProductPerformanceResponse.cs b/EstateReportingAPI.Models/ProductPerformanceResponse.cs new file mode 100644 index 0000000..abd1617 --- /dev/null +++ b/EstateReportingAPI.Models/ProductPerformanceResponse.cs @@ -0,0 +1,7 @@ +namespace EstateReportingAPI.Models; + +public class ProductPerformanceResponse +{ + public List ProductDetails { get; set; } + public ProductPerformanceSummary Summary { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI.Models/ProductPerformanceSummary.cs b/EstateReportingAPI.Models/ProductPerformanceSummary.cs new file mode 100644 index 0000000..d18f552 --- /dev/null +++ b/EstateReportingAPI.Models/ProductPerformanceSummary.cs @@ -0,0 +1,9 @@ +namespace EstateReportingAPI.Models; + +public class ProductPerformanceSummary +{ + public Int32 TotalProducts { get; set; } + public Int32 TotalCount { get; set; } + public Decimal TotalValue { get; set; } + public Decimal AveragePerProduct { get; set; } +} \ No newline at end of file diff --git a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs index 616a99d..ad30299 100644 --- a/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs +++ b/EstateReportingAPI/Bootstrapper/MediatorRegistry.cs @@ -24,6 +24,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 5bb3ea2..234554f 100644 --- a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs +++ b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs @@ -28,5 +28,8 @@ public static void MapTransactionEndpoints(this IEndpointRouteBuilder app) .WithStandardProduces(); group.MapPost("transactionsummarybyoperatorreport", TransactionHandler.TransactionSummaryByOperatorReport) .WithStandardProduces(); + group.MapGet("productperformancereport", TransactionHandler.ProductPerformanceReport) + .WithStandardProduces(); + } } \ No newline at end of file diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index 5521543..689a2ad 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -76,8 +76,15 @@ TransactionDetailReportResponse SuccessFactory (Models.TransactionDetailReportRe SettlementReference = t.SettlementReference, Status = t.Status, Type = t.Type, - Value = t.Value - }) + Value = t.Value, + MerchantId = t.MerchantId, + OperatorReportingId = t.OperatorReportingId, + OperatorId = t.OperatorId, + MerchantReportingId = t.MerchantReportingId, + ProductId = t.ProductId, + ProductReportingId = t.ProductReportingId + + }) .ToList() }; @@ -114,6 +121,33 @@ TransactionSummaryByMerchantResponse SuccessFactory (Models.TransactionSummaryBy return ResponseFactory.FromResult(result, SuccessFactory); } + public static async Task ProductPerformanceReport([FromHeader] Guid estateId, + [FromQuery] DateTime startDate, + [FromQuery] DateTime endDate, + IMediator mediator, + CancellationToken cancellationToken) { + var query = new TransactionQueries.ProductPerformanceQuery(estateId, startDate, endDate); + var result = await mediator.Send(query, cancellationToken); + ProductPerformanceResponse SuccessFactory(Models.ProductPerformanceResponse r) => + new ProductPerformanceResponse + { + Summary = new ProductPerformanceSummary { TotalProducts = r.Summary.TotalProducts, TotalCount = r.Summary.TotalCount, TotalValue = r.Summary.TotalValue, AveragePerProduct = r.Summary.AveragePerProduct }, + ProductDetails = r.ProductDetails.Select(p => new ProductPerformanceDetail + { + ProductId = p.ProductId, + ProductName = p.ProductName, + ProductReportingId = p.ProductReportingId, + ContractId = p.ContractId, + ContractReportingId = p.ContractReportingId, + PercentageOfTotal = p.PercentageOfTotal, + TransactionCount = p.TransactionCount, + TransactionValue = p.TransactionValue, + }) + .ToList() + }; + return ResponseFactory.FromResult(result, SuccessFactory); + } + public static async Task TransactionSummaryByOperatorReport([FromHeader] Guid estateId, [FromBody] TransactionSummaryByOperatorRequest request, IMediator mediator, CancellationToken cancellationToken)