From 5a90e997d5657fe72a4c28be418b98f46fb02ff7 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Jul 2026 17:21:37 +0100 Subject: [PATCH 1/2] Add operator info to metrics, projections, and endpoints - Added OperatorName to performance projections and queries - Updated MetricItem with Type property and new categories - Included operator in top product metric grouping/description - Extended DrillDownTransaction with Operator property - Updated TransactionHandler to format product with operator name - Removed duplicate/misspelled endpoint mapping --- .../ReportingManager.cs | 35 ++++++++++++------- .../MerchantDailyPerformanceSummaryRequest.cs | 3 ++ .../Endpoints/TransactionEndpoints.cs | 2 -- .../Handlers/TransactionHandler.cs | 2 +- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 58132d9..7ed86ef 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -2,6 +2,7 @@ using SimpleResults; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.DynamicLinq; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal; using TransactionProcessor.Database.Contexts; using TransactionProcessor.Database.Entities; using TransactionProcessor.Database.Entities.Summary; @@ -1996,6 +1997,7 @@ private sealed class MerchantTransactionFinalProjection { private sealed class MerchantDailyPerformanceGroupProjection { public int ContractProductReportingId { get; init; } public string? ProductName { get; init; } + public string OperatorName { get; init; } public bool IsAuthorised { get; init; } public int SalesCount { get; init; } public decimal SalesValue { get; init; } @@ -2004,6 +2006,7 @@ private sealed class MerchantDailyPerformanceGroupProjection { private sealed class MerchantDailyPerformanceRecentSaleProjection { public string Reference { get; init; } public string? Product { get; init; } + public String Operator { get; init; } public string Status { get; init; } public decimal Amount { get; init; } public DateTime TransactionDateTime { get; init; } @@ -2040,14 +2043,14 @@ private static List BuildMerchantDailyPerformanceMetrics(List metrics = new() { - new() { Title = "Total Sales Count", Value = totalSalesCount, Description = "All sales transactions in the range", Category = 0 }, - new() { Title = "Total Sales Value", Value = totalSalesValue, Description = "All sales value in the range", Category = 1 }, - new() { Title = "Successful Sales Count", Value = successfulSalesCount, Description = "Authorised sales count in the range", Category = 2 }, - new() { Title = "Successful Sales Value", Value = successfulSalesValue, Description = "Authorised sales value in the range", Category = 3 }, - new() { Title = "Failed Sales Count", Value = failedSalesCount, Description = "Declined sales count in the range", Category = 4 }, - new() { Title = "Failed Sales Value", Value = failedSalesValue, Description = "Declined sales value in the range", Category = 5 }, - new() { Title = "Average Sales Count", Value = averageSalesCount, Description = "Average sales count per day in the range", Category = 6 }, - new() { Title = "Average Sales Value", Value = averageSalesValue, Description = "Average value per sale in the range", Category = 7 } + new() { Title = "Total Sales Count", Value = totalSalesCount, Description = "All sales transactions in the range", Category = 1, Type = 0}, + new() { Title = "Total Sales Value", Value = totalSalesValue, Description = "All sales value in the range", Category = 1, Type = 1 }, + new() { Title = "Successful Sales Count", Value = successfulSalesCount, Description = "Authorised sales count in the range", Category = 2, Type = 2 }, + new() { Title = "Successful Sales Value", Value = successfulSalesValue, Description = "Authorised sales value in the range", Category = 2, Type = 3 }, + new() { Title = "Failed Sales Count", Value = failedSalesCount, Description = "Declined sales count in the range", Category = 3, Type = 4 }, + new() { Title = "Failed Sales Value", Value = failedSalesValue, Description = "Declined sales value in the range", Category = 3, Type = 5 }, + new() { Title = "Average Sales Count", Value = averageSalesCount, Description = "Average sales count per day in the range", Category = 4, Type = 6 }, + new() { Title = "Average Sales Value", Value = averageSalesValue, Description = "Average value per sale in the range", Category = 4, Type = 7 } }; MetricItem? topProductMetric = BuildTopProductMetric(groupedTransactions); @@ -2059,10 +2062,11 @@ private static List BuildMerchantDailyPerformanceMetrics(List groupedTransactions) { var topProduct = groupedTransactions - .GroupBy(x => new { x.ContractProductReportingId, x.ProductName }) + .GroupBy(x => new { x.ContractProductReportingId, x.OperatorName, x.ProductName }) .Select(g => new { g.Key.ContractProductReportingId, - g.Key.ProductName, + g.Key.ProductName, + g.Key.OperatorName, SalesCount = g.Sum(x => x.SalesCount), SalesValue = g.Sum(x => x.SalesValue) }) @@ -2076,8 +2080,9 @@ private static List BuildMerchantDailyPerformanceMetrics(List>> LoadMe from t in context.Transactions join m in context.Merchants on t.MerchantId equals m.MerchantId join cp in context.ContractProducts on new { t.ContractProductId, t.ContractId } equals new { cp.ContractProductId, cp.ContractId } + join op in context.Operators on t.OperatorId equals op.OperatorId where t.TransactionType == "Sale" && t.TransactionDate >= startDate && t.TransactionDate <= endDate @@ -2097,6 +2103,7 @@ join m in context.Merchants on t.MerchantId equals m.MerchantId group t by new { cp.ContractProductReportingId, + op.Name, cp.ProductName, t.IsAuthorised } @@ -2105,6 +2112,7 @@ into g { ContractProductReportingId = g.Key.ContractProductReportingId, ProductName = g.Key.ProductName, + OperatorName = g.Key.Name, IsAuthorised = g.Key.IsAuthorised, SalesCount = g.Count(), SalesValue = g.Sum(x => x.TransactionAmount) @@ -2122,6 +2130,7 @@ private async Task>> L (from t in context.Transactions join m in context.Merchants on t.MerchantId equals m.MerchantId join cp in context.ContractProducts on new { t.ContractProductId, t.ContractId } equals new { cp.ContractProductId, cp.ContractId } + join op in context.Operators on t.OperatorId equals op.OperatorId where t.TransactionType == "Sale" && t.TransactionDate >= startDate && t.TransactionDate <= endDate @@ -2131,6 +2140,7 @@ orderby t.TransactionDateTime descending { Reference = t.TransactionNumber, Product = cp.ProductName, + Operator = op.Name, Status = t.IsAuthorised ? "Successful" : "Failed", Amount = t.TransactionAmount, TransactionDateTime = t.TransactionDateTime @@ -2143,6 +2153,7 @@ private static List MapMerchantDailyPerformanceRecentSales return recentSales.Select(x => new DrillDownTransaction { Reference = x.Reference, Product = x.Product, + Operator = x.Operator, Status = x.Status, Amount = x.Amount, TransactionDateTime = x.TransactionDateTime diff --git a/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs b/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs index 3c19ec5..db6b03a 100644 --- a/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs +++ b/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs @@ -25,6 +25,7 @@ public class MetricItem { public string Description { get; set; } public Int32 Category { get; set; } + public Int32 Type { get; set; } } public class DrillDownTransaction { @@ -32,6 +33,8 @@ public class DrillDownTransaction { public string Product { get; set; } + public string Operator { get; set; } + public string Status { get; set; } public Decimal Amount { get; set; } diff --git a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs index aa2a9f5..7f5812d 100644 --- a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs +++ b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs @@ -38,8 +38,6 @@ public static void MapTransactionEndpoints(this IEndpointRouteBuilder app) .WithStandardProduces>(); group.MapPost("merchantdailyperformancesummary", TransactionHandler.MerchantDailyPerformanceSummary) .WithStandardProduces(); - group.MapPost("dailymerchaantprformancesummary", TransactionHandler.MerchantDailyPerformanceSummary) - .WithStandardProduces(); } } diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index 2d24d17..adec68a 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -333,7 +333,7 @@ MerchantDailyPerformanceSummaryResponse SuccessFactory(Models.MerchantDailyPerfo DrillDownTransactions = r.DrillDownTransactions?.Select(transaction => new DrillDownTransaction { Reference = transaction.Reference, - Product = transaction.Product, + Product = $"{transaction.Operator} {transaction.Product}", Status = transaction.Status, Amount = transaction.Amount, TransactionDateTime = transaction.TransactionDateTime From bc3c2b8b6ef927cba04d0129b1cb639f97471d76 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Thu, 9 Jul 2026 19:10:12 +0100 Subject: [PATCH 2/2] fixed test failure --- .../TransactionsEndpointTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs index 5516d52..93f3f89 100644 --- a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs +++ b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs @@ -1107,7 +1107,7 @@ public async Task TransactionsEndpoint_MerchantDailyPerformanceSummary_ReturnsMe TransactionProcessor.Database.Entities.Merchant merchant = this.merchantsList.Single(m => m.Name == "Test Merchant 1"); var contract = this.contractList.Single(c => c.contractName == "Safaricom Contract"); var product = this.contractProducts.Single(cp => cp.Key == contract.contractId).Value.First(); - + var operatorRecord = this.operatorsList.Single(o => o.OperatorId == contract.operatorId); DateTime transactionDate = DateTime.Now.Date; List transactions = new(); @@ -1155,7 +1155,7 @@ public async Task TransactionsEndpoint_MerchantDailyPerformanceSummary_ReturnsMe response.Metrics.Single(m => m.Title == "Successful Sales Count").Value.ShouldBe(3); response.Metrics.Single(m => m.Title == "Failed Sales Count").Value.ShouldBe(3); response.Metrics.Single(m => m.Title == "Top Product Sales Count").Value.ShouldBe(6); - response.Metrics.Single(m => m.Title == "Top Product Sales Count").Description.ShouldBe(product.productName); + response.Metrics.Single(m => m.Title == "Top Product Sales Count").Description.ShouldBe($"{operatorRecord.Name} {product.productName}"); response.DrillDownTransactions.Count.ShouldBe(5); response.DrillDownTransactions.Select(x => x.Reference).ShouldBe(new[] { "0006", "0005", "0004", "0003", "0002" });