From ff49c35d4963371eb2fcac27c15a2e9d49ca23bf Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Sun, 15 Feb 2026 11:48:25 +0000 Subject: [PATCH 1/2] wip --- EstateReportingAPI.BusinessLogic/ReportingManager.cs | 1 - EstateReportingAPI/Handlers/TransactionHandler.cs | 11 +++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 5c37e00..1d84f95 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -786,7 +786,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 diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index 5521543..c784dd5 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() }; From 785059dbcdc71b92cbcf8e3aa1c57b8c17b2d2fe Mon Sep 17 00:00:00 2001 From: Stuart Ferguson Date: Mon, 16 Feb 2026 10:38:26 +0000 Subject: [PATCH 2/2] Add Product Performance report endpoint and refactor tests Introduced a new Product Performance report feature, including backend query logic, MediatR handler, and a new API endpoint for retrieving product sales performance by date range. Added supporting models and response types. Refactored and reorganized transaction, merchant, operator, and contract endpoint tests into dedicated files for clarity. No breaking changes to existing endpoints; all previous functionality remains intact. --- .../Queries/TransactionQueries.cs | 1 + .../ReportingManager.cs | 106 +- .../TransactionRequestHandler.cs | 8 +- .../MerchantDetail.cs | 25 + .../MerchantDetailSummary.cs | 15 + .../OperatorDetail.cs | 26 + .../OperatorDetailSummary.cs | 16 + .../TransactionDetailReportResponse.cs | 98 +- .../TransactionSummaryByMerchantResponse.cs | 12 + .../TransactionSummaryByOperatorResponse.cs | 12 + .../CalendarEndpointTests.cs | 1261 ----------------- .../ContractEndPointTests.cs | 124 ++ .../EstateEndpointTests.cs | 55 + .../MerchantEndpointTests.cs | 193 +++ .../OperatorEndpointTests.cs | 48 + .../TransactionsEndpointTests.cs | 944 ++++++++++++ .../ProductPerformanceDetail.cs | 13 + .../ProductPerformanceResponse.cs | 7 + .../ProductPerformanceSummary.cs | 9 + .../Bootstrapper/MediatorRegistry.cs | 1 + .../Endpoints/TransactionEndpoints.cs | 3 + .../Handlers/TransactionHandler.cs | 27 + 22 files changed, 1669 insertions(+), 1335 deletions(-) create mode 100644 EstateReportingAPI.DataTrasferObjects/MerchantDetail.cs create mode 100644 EstateReportingAPI.DataTrasferObjects/MerchantDetailSummary.cs create mode 100644 EstateReportingAPI.DataTrasferObjects/OperatorDetail.cs create mode 100644 EstateReportingAPI.DataTrasferObjects/OperatorDetailSummary.cs create mode 100644 EstateReportingAPI.DataTrasferObjects/TransactionSummaryByMerchantResponse.cs create mode 100644 EstateReportingAPI.DataTrasferObjects/TransactionSummaryByOperatorResponse.cs create mode 100644 EstateReportingAPI.IntegrationTests/ContractEndPointTests.cs create mode 100644 EstateReportingAPI.IntegrationTests/EstateEndpointTests.cs create mode 100644 EstateReportingAPI.IntegrationTests/MerchantEndpointTests.cs create mode 100644 EstateReportingAPI.IntegrationTests/OperatorEndpointTests.cs create mode 100644 EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs create mode 100644 EstateReportingAPI.Models/ProductPerformanceDetail.cs create mode 100644 EstateReportingAPI.Models/ProductPerformanceResponse.cs create mode 100644 EstateReportingAPI.Models/ProductPerformanceSummary.cs 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 1d84f95..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 @@ -1345,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 c784dd5..689a2ad 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -121,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)