diff --git a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs index f2873b8..ac97904 100644 --- a/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs +++ b/EstateReportingAPI.BusinessLogic/Queries/TransactionQueries.cs @@ -14,4 +14,5 @@ public record TransactionSummaryByMerchantQuery(Guid EstateId, TransactionSummar public record TransactionSummaryByOperatorQuery(Guid EstateId, TransactionSummaryByOperatorRequest Request) : IRequest>; public record ProductPerformanceQuery(Guid EstateId, DateTime StartDate, DateTime EndDate) : IRequest>; public record TodaysSalesByHour(Guid estateId, DateTime comparisonDate) : IRequest>>; + public record MerchantDailyPerformanceSummaryQuery(Guid EstateId, MerchantDailyPerformanceSummaryRequest Request) : IRequest>; } \ No newline at end of file diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 9723b15..bc3a79a 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -65,6 +65,9 @@ Task> GetFileImportLog(FileImportLogQueries.GetFileImportL CancellationToken cancellationToken); Task>> GetFileProfileConfigurationList(FileProfileConfigurationQueries.GetFileProfileConfigurationListQuery request, CancellationToken cancellationToken); + + Task> GetMerchantDailyPerformanceSummary(TransactionQueries.MerchantDailyPerformanceSummaryQuery request, + CancellationToken cancellationToken); } public class ReportingManager : IReportingManager { @@ -1550,6 +1553,36 @@ join o in context.Operators on c.OperatorId equals o.OperatorId return Result.Success(result.Data); } + public async Task> GetMerchantDailyPerformanceSummary(TransactionQueries.MerchantDailyPerformanceSummaryQuery request, + CancellationToken cancellationToken) { + using ResolvedDbContext? resolvedContext = this.Resolver.Resolve(EstateManagementDatabaseName, request.EstateId.ToString()); + await using EstateManagementContext context = resolvedContext.Context; + + DateTime startDate = request.Request.StartDate.Date; + DateTime endDate = request.Request.EndDate.Date; + int dayCount = Math.Max((endDate - startDate).Days + 1, 1); + + var groupedTransactionsResult = await LoadMerchantDailyPerformanceGroups(context, request, startDate, endDate, cancellationToken); + if (groupedTransactionsResult.IsFailed) + return ResultHelpers.CreateFailure(groupedTransactionsResult); + + var groupedTransactions = groupedTransactionsResult.Data; + if (groupedTransactions.Any() == false) { + return Result.Success(BuildEmptyMerchantDailyPerformanceSummaryResponse()); + } + + List metrics = BuildMerchantDailyPerformanceMetrics(groupedTransactions, dayCount); + + var recentSalesResult = await LoadMerchantDailyPerformanceRecentSales(context, request, startDate, endDate, cancellationToken); + if (recentSalesResult.IsFailed) + return ResultHelpers.CreateFailure(recentSalesResult); + + return Result.Success(new MerchantDailyPerformanceSummaryResponse { + Metrics = metrics, + DrillDownTransactions = MapMerchantDailyPerformanceRecentSales(recentSalesResult.Data) + }); + } + private async Task GetSettlementSummary(IQueryable query, CancellationToken cancellationToken) { // Get the settleed fees summary @@ -1711,6 +1744,162 @@ private sealed class MerchantTransactionFinalProjection { public decimal AuthorisedPercentage { get; init; } } + private sealed class MerchantDailyPerformanceGroupProjection { + public int ContractProductReportingId { get; init; } + public string? ProductName { get; init; } + public bool IsAuthorised { get; init; } + public int SalesCount { get; init; } + public decimal SalesValue { get; init; } + } + + private sealed class MerchantDailyPerformanceRecentSaleProjection { + public string Reference { get; init; } + public string? Product { get; init; } + public string Status { get; init; } + public decimal Amount { get; init; } + public DateTime TransactionDateTime { get; init; } + } + + private static MerchantDailyPerformanceSummaryResponse BuildEmptyMerchantDailyPerformanceSummaryResponse() { + return new MerchantDailyPerformanceSummaryResponse { + Metrics = BuildMerchantDailyPerformanceBaseMetrics() + }; + } + + private static List BuildMerchantDailyPerformanceBaseMetrics() { + return new List { + new() { Title = "Total Sales Count", Value = 0, Description = "All sales transactions in the range", Category = 0 }, + new() { Title = "Total Sales Value", Value = 0, Description = "All sales value in the range", Category = 1 }, + new() { Title = "Successful Sales Count", Value = 0, Description = "Authorised sales count in the range", Category = 2 }, + new() { Title = "Successful Sales Value", Value = 0, Description = "Authorised sales value in the range", Category = 3 }, + new() { Title = "Failed Sales Count", Value = 0, Description = "Declined sales count in the range", Category = 4 }, + new() { Title = "Failed Sales Value", Value = 0, Description = "Declined sales value in the range", Category = 5 }, + new() { Title = "Average Sales Count", Value = 0, Description = "Average sales count per day in the range", Category = 6 }, + new() { Title = "Average Sales Value", Value = 0, Description = "Average value per sale in the range", Category = 7 } + }; + } + + private static List BuildMerchantDailyPerformanceMetrics(List groupedTransactions, + int dayCount) { + int totalSalesCount = groupedTransactions.Sum(x => x.SalesCount); + decimal totalSalesValue = groupedTransactions.Sum(x => x.SalesValue); + int successfulSalesCount = groupedTransactions.Where(x => x.IsAuthorised).Sum(x => x.SalesCount); + decimal successfulSalesValue = groupedTransactions.Where(x => x.IsAuthorised).Sum(x => x.SalesValue); + int failedSalesCount = groupedTransactions.Where(x => !x.IsAuthorised).Sum(x => x.SalesCount); + decimal failedSalesValue = groupedTransactions.Where(x => !x.IsAuthorised).Sum(x => x.SalesValue); + decimal averageSalesCount = (decimal)totalSalesCount / dayCount; + decimal averageSalesValue = totalSalesCount == 0 ? 0m : totalSalesValue / totalSalesCount; + + 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 } + }; + + MetricItem? topProductMetric = BuildTopProductMetric(groupedTransactions); + if (topProductMetric != null) + metrics.Add(topProductMetric); + + return metrics; + } + + private static MetricItem? BuildTopProductMetric(List groupedTransactions) { + var topProduct = groupedTransactions + .GroupBy(x => new { x.ContractProductReportingId, x.ProductName }) + .Select(g => new { + g.Key.ContractProductReportingId, + g.Key.ProductName, + SalesCount = g.Sum(x => x.SalesCount), + SalesValue = g.Sum(x => x.SalesValue) + }) + .OrderByDescending(x => x.SalesCount) + .ThenBy(x => x.ProductName) + .FirstOrDefault(); + + if (topProduct == null) + return null; + + return new MetricItem { + Title = "Top Product Sales Count", + Value = topProduct.SalesCount, + Description = topProduct.ProductName ?? "Unknown product", + Category = 8 + }; + } + + private async Task>> LoadMerchantDailyPerformanceGroups(EstateManagementContext context, + TransactionQueries.MerchantDailyPerformanceSummaryQuery request, + DateTime startDate, + DateTime endDate, + CancellationToken cancellationToken) { + var query = + 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 } + where t.TransactionType == "Sale" + && t.TransactionDate >= startDate + && t.TransactionDate <= endDate + && m.MerchantReportingId == request.Request.MerchantReportingId + group t by new + { + cp.ContractProductReportingId, + cp.ProductName, + t.IsAuthorised + } + into g + select new MerchantDailyPerformanceGroupProjection + { + ContractProductReportingId = g.Key.ContractProductReportingId, + ProductName = g.Key.ProductName, + IsAuthorised = g.Key.IsAuthorised, + SalesCount = g.Count(), + SalesValue = g.Sum(x => x.TransactionAmount) + }; + + return await ExecuteQuerySafeToList(query, cancellationToken, "Error retrieving merchant daily performance summary"); + } + + private async Task>> LoadMerchantDailyPerformanceRecentSales(EstateManagementContext context, + TransactionQueries.MerchantDailyPerformanceSummaryQuery request, + DateTime startDate, + DateTime endDate, + CancellationToken cancellationToken) { + var query = + (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 } + where t.TransactionType == "Sale" + && t.TransactionDate >= startDate + && t.TransactionDate <= endDate + && m.MerchantReportingId == request.Request.MerchantReportingId + orderby t.TransactionDateTime descending + select new MerchantDailyPerformanceRecentSaleProjection + { + Reference = t.TransactionNumber, + Product = cp.ProductName, + Status = t.IsAuthorised ? "Successful" : "Failed", + Amount = t.TransactionAmount, + TransactionDateTime = t.TransactionDateTime + }).Take(5); + + return await ExecuteQuerySafeToList(query, cancellationToken, "Error retrieving merchant recent sales"); + } + + private static List MapMerchantDailyPerformanceRecentSales(List recentSales) { + return recentSales.Select(x => new DrillDownTransaction { + Reference = x.Reference, + Product = x.Product, + Status = x.Status, + Amount = x.Amount, + TransactionDateTime = x.TransactionDateTime + }).ToList(); + } + private sealed class TransactionDetailQueryResult { public Guid TransactionId { get; init; } public DateTime TransactionDateTime { get; init; } diff --git a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs index 04ae197..a94f2dc 100644 --- a/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs +++ b/EstateReportingAPI.BusinessLogic/RequestHandlers/TransactionRequestHandler.cs @@ -11,7 +11,8 @@ public class TransactionRequestHandler : IRequestHandler>, IRequestHandler>, IRequestHandler>, - IRequestHandler>> + IRequestHandler>>, + IRequestHandler> { private readonly IReportingManager Manager; @@ -56,4 +57,9 @@ public async Task>> Handle(TransactionQueries.Tod CancellationToken cancellationToken) { return await this.Manager.GetTodaysSalesByHour(request, cancellationToken); } + + public async Task> Handle(TransactionQueries.MerchantDailyPerformanceSummaryQuery request, + CancellationToken cancellationToken) { + return await this.Manager.GetMerchantDailyPerformanceSummary(request, cancellationToken); + } } \ No newline at end of file diff --git a/EstateReportingAPI.DataTrasferObjects/MerchantDailyPerformanceSummaryRequest.cs b/EstateReportingAPI.DataTrasferObjects/MerchantDailyPerformanceSummaryRequest.cs new file mode 100644 index 0000000..e0bd88b --- /dev/null +++ b/EstateReportingAPI.DataTrasferObjects/MerchantDailyPerformanceSummaryRequest.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EstateReportingAPI.DataTransferObjects +{ + public class MerchantDailyPerformanceSummaryRequest + { + public Int32 MerchantReportingId { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + } + + public sealed class MerchantDailyPerformanceSummaryResponse + { + public List Metrics { get; set; } = []; + + public List DrillDownTransactions { get; set; } = []; + } + + public sealed class MetricItem + { + public string Title { get; set; } + + public Decimal Value { get; set; } + + public string Description { get; set; } + + public int Category { get; set; } + } + + public sealed class DrillDownTransaction + { + public string Reference { get; set; } + + public string Product { get; set; } + + public string Status { get; set; } + + public Decimal Amount { get; set; } + + public DateTime TransactionDateTime { get; set; } + } +} diff --git a/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs b/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs index 352ab8d..0a559ab 100644 --- a/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs +++ b/EstateReportingAPI.IntegrationTests/ControllerTestsBase.cs @@ -169,4 +169,4 @@ public override async Task CreateSubscriptions(){ return sqlContainer.Item2.GetMappedPublicPort(1433); } -} \ No newline at end of file +} diff --git a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs index 054a97b..8d42947 100644 --- a/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs +++ b/EstateReportingAPI.IntegrationTests/TransactionsEndpointTests.cs @@ -1071,4 +1071,65 @@ public async Task TransactionsEndpoint_TodaysSalesByHour_SummaryDataReturned() } } -} \ No newline at end of file + + [Fact] + public async Task TransactionsEndpoint_MerchantDailyPerformanceSummary_ReturnsMetricsAndLastFiveSales() + { + 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(); + + DateTime transactionDate = DateTime.Now.Date; + List transactions = new(); + + for (int i = 1; i <= 6; i++) + { + DateTime transactionDateTime = transactionDate.AddHours(9 + i); + string responseCode = i % 2 == 0 ? "1009" : "0000"; + + Transaction transaction = await this.helper.BuildTransactionX( + transactionDateTime, + merchant.MerchantId, + contract.operatorId, + contract.contractId, + product.productId, + responseCode, + product.productValue, + i); + + transactions.Add(transaction); + } + + await this.helper.AddTransactionsX(transactions); + + MerchantDailyPerformanceSummaryRequest request = new() + { + MerchantReportingId = merchant.MerchantReportingId, + StartDate = transactionDate, + EndDate = transactionDate + }; + + var result = await this.CreateAndSendHttpRequestMessage( + $"{this.BaseRoute}/merchantdailyperformancesummary", + StringSerialiser.Serialise(request), + CancellationToken.None); + + result.IsSuccess.ShouldBeTrue(); + + MerchantDailyPerformanceSummaryResponse response = result.Data; + response.ShouldNotBeNull(); + response.Metrics.ShouldNotBeNull(); + response.DrillDownTransactions.ShouldNotBeNull(); + + response.Metrics.Count.ShouldBe(9); + response.Metrics.Single(m => m.Title == "Total Sales Count").Value.ShouldBe(6); + 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.DrillDownTransactions.Count.ShouldBe(5); + response.DrillDownTransactions.Select(x => x.Reference).ShouldBe(new[] { "0006", "0005", "0004", "0003", "0002" }); + response.DrillDownTransactions.Select(x => x.Status).ShouldBe(new[] { "Failed", "Successful", "Failed", "Successful", "Failed" }); + } +} diff --git a/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs b/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs new file mode 100644 index 0000000..3c19ec5 --- /dev/null +++ b/EstateReportingAPI.Models/MerchantDailyPerformanceSummaryRequest.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace EstateReportingAPI.Models +{ + public class MerchantDailyPerformanceSummaryRequest + { + public Int32 MerchantReportingId { get; set; } + public DateTime StartDate { get; set; } + public DateTime EndDate { get; set; } + } + + public class MerchantDailyPerformanceSummaryResponse { + public List Metrics { get; set; } = []; + + public List DrillDownTransactions { get; set; } = []; + } + + public class MetricItem { + public string Title { get; set; } + + public Decimal Value { get; set; } + + public string Description { get; set; } + + public Int32 Category { get; set; } + } + + public class DrillDownTransaction { + public string Reference { get; set; } + + public string Product { get; set; } + + public string Status { get; set; } + + public Decimal Amount { get; set; } + + public DateTime TransactionDateTime { get; set; } + } +} diff --git a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs index 245cb21..4ecc736 100644 --- a/EstateReportingAPI/Endpoints/TransactionEndpoints.cs +++ b/EstateReportingAPI/Endpoints/TransactionEndpoints.cs @@ -32,6 +32,10 @@ public static void MapTransactionEndpoints(this IEndpointRouteBuilder app) .WithStandardProduces(); group.MapGet("todayssalesbyhour", TransactionHandler.TodaysSalesByHour) .WithStandardProduces>(); + group.MapPost("merchantdailyperformancesummary", TransactionHandler.MerchantDailyPerformanceSummary) + .WithStandardProduces(); + group.MapPost("dailymerchaantprformancesummary", TransactionHandler.MerchantDailyPerformanceSummary) + .WithStandardProduces(); } -} \ No newline at end of file +} diff --git a/EstateReportingAPI/Handlers/MerchantHandler.cs b/EstateReportingAPI/Handlers/MerchantHandler.cs index 54f85cb..d3238c1 100644 --- a/EstateReportingAPI/Handlers/MerchantHandler.cs +++ b/EstateReportingAPI/Handlers/MerchantHandler.cs @@ -7,21 +7,13 @@ namespace EstateReportingAPI.Handlers; -public static class MerchantHandler -{ +public static class MerchantHandler { public static async Task GetMerchantKpis([FromHeader] Guid estateId, IMediator mediator, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { MerchantQueries.GetTransactionKpisQuery query = new(estateId); Result result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => - new DataTransferObjects.MerchantKpi() - { - MerchantsWithNoSaleInLast7Days =r.MerchantsWithNoSaleInLast7Days, - MerchantsWithNoSaleToday = r.MerchantsWithNoSaleToday, - MerchantsWithSaleInLastHour = r.MerchantsWithSaleInLastHour - }); + return ResponseFactory.FromResult(result, r => new DataTransferObjects.MerchantKpi() { MerchantsWithNoSaleInLast7Days = r.MerchantsWithNoSaleInLast7Days, MerchantsWithNoSaleToday = r.MerchantsWithNoSaleToday, MerchantsWithSaleInLastHour = r.MerchantsWithSaleInLastHour }); } public static async Task GetRecentMerchants([FromHeader] Guid estateId, @@ -29,8 +21,7 @@ public static async Task GetRecentMerchants([FromHeader] Guid estateId, CancellationToken cancellationToken) { MerchantQueries.GetRecentMerchantsQuery query = new(estateId); Result> result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => r.Select(m => new EstateReportingAPI.DataTransferObjects.Merchant - { + return ResponseFactory.FromResult(result, r => r.Select(m => new EstateReportingAPI.DataTransferObjects.Merchant { MerchantReportingId = m.MerchantReportingId, MerchantId = m.MerchantId, Name = m.Name, @@ -47,7 +38,6 @@ public static async Task GetRecentMerchants([FromHeader] Guid estateId, ContactName = m.ContactName, ContactEmail = m.ContactEmail, ContactPhone = m.ContactPhone - }).OrderByDescending(m => m.CreatedDateTime).ToList()); } @@ -58,15 +48,13 @@ public static async Task GetMerchants([FromHeader] Guid estateId, [FromQuery] String? region, [FromQuery] String? postCode, IMediator mediator, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { // Build the query options MerchantQueries.MerchantQueryOptions queryOptions = new(name, reference, settlementSchedule, region, postCode); MerchantQueries.GetMerchantsQuery query = new(estateId, queryOptions); Result> result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => r.Select(m => new EstateReportingAPI.DataTransferObjects.Merchant - { + return ResponseFactory.FromResult(result, r => r.Select(m => new EstateReportingAPI.DataTransferObjects.Merchant { MerchantReportingId = m.MerchantReportingId, MerchantId = m.MerchantId, Name = m.Name, @@ -89,14 +77,12 @@ public static async Task GetMerchants([FromHeader] Guid estateId, public static async Task GetMerchant([FromHeader] Guid estateId, [FromRoute] Guid merchantId, IMediator mediator, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { // Build the query options MerchantQueries.GetMerchantQuery query = new(estateId, merchantId); Result result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => new EstateReportingAPI.DataTransferObjects.Merchant - { + return ResponseFactory.FromResult(result, r => new EstateReportingAPI.DataTransferObjects.Merchant { MerchantReportingId = r.MerchantReportingId, MerchantId = r.MerchantId, Name = r.Name, @@ -140,20 +126,17 @@ public static async Task GetMerchantOperators([FromHeader] Guid estateI public static async Task GetMerchantContracts([FromHeader] Guid estateId, [FromRoute] Guid merchantId, IMediator mediator, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { MerchantQueries.GetMerchantContractsQuery query = new(estateId, merchantId); Result> result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => r.Select(c => new DataTransferObjects.MerchantContract() - { + return ResponseFactory.FromResult(result, r => r.Select(c => new DataTransferObjects.MerchantContract() { ContractId = c.ContractId, ContractName = c.ContractName, OperatorName = c.OperatorName, IsDeleted = c.IsDeleted, MerchantId = c.MerchantId, - ContractProducts = c.ContractProducts.Select(p => new DataTransferObjects.MerchantContractProduct() - { + ContractProducts = c.ContractProducts.Select(p => new DataTransferObjects.MerchantContractProduct() { ProductId = p.ProductId, ProductName = p.ProductName, ContractId = c.ContractId, @@ -166,34 +149,21 @@ public static async Task GetMerchantContracts([FromHeader] Guid estateI public static async Task GetMerchantDevices([FromHeader] Guid estateId, [FromRoute] Guid merchantId, IMediator mediator, - CancellationToken cancellationToken) - { + CancellationToken cancellationToken) { MerchantQueries.GetMerchantDevicesQuery query = new(estateId, merchantId); Result> result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => r.Select(c => new DataTransferObjects.MerchantDevice() - { - IsDeleted = c.IsDeleted, - MerchantId = c.MerchantId, - DeviceId = c.DeviceId, - DeviceIdentifier = c.DeviceIdentifier - }).ToList()); + return ResponseFactory.FromResult(result, r => r.Select(c => new DataTransferObjects.MerchantDevice() { IsDeleted = c.IsDeleted, MerchantId = c.MerchantId, DeviceId = c.DeviceId, DeviceIdentifier = c.DeviceIdentifier }).ToList()); } public static async Task GetMerchantOpeningHours([FromHeader] Guid estateId, - [FromRoute] Guid merchantId, - IMediator mediator, - CancellationToken cancellationToken) { + [FromRoute] Guid merchantId, + IMediator mediator, + CancellationToken cancellationToken) { MerchantQueries.GetMerchantOpeningHoursQuery query = new(estateId, merchantId); Result> result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => r.Select(c => new DataTransferObjects.MerchantOpeningHour() - { - MerchantId = c.MerchantId, - DayOfWeek = c.DayOfWeek, - OpeningTime = c.OpeningTime, - ClosingTime = c.ClosingTime - }).ToList()); + return ResponseFactory.FromResult(result, r => r.Select(c => new DataTransferObjects.MerchantOpeningHour() { MerchantId = c.MerchantId, DayOfWeek = c.DayOfWeek, OpeningTime = c.OpeningTime, ClosingTime = c.ClosingTime }).ToList()); } public static async Task GetMerchantSchedule([FromHeader] Guid estateId, @@ -204,12 +174,6 @@ public static async Task GetMerchantSchedule([FromHeader] Guid estateId MerchantQueries.GetMerchantScheduleQuery query = new(estateId, merchantId, year); Result result = await mediator.Send(query, cancellationToken); - return ResponseFactory.FromResult(result, r => new DataTransferObjects.MerchantScheduleResponse() { - Year = r.Year, - Months = r.Months.Select(m => new DataTransferObjects.MerchantScheduleMonthResponse() { - Month = m.Month, - ClosedDays = m.ClosedDays - }).ToList() - }); + return ResponseFactory.FromResult(result, r => new DataTransferObjects.MerchantScheduleResponse() { Year = r.Year, Months = r.Months.Select(m => new DataTransferObjects.MerchantScheduleMonthResponse() { Month = m.Month, ClosedDays = m.ClosedDays }).ToList() }); } } \ No newline at end of file diff --git a/EstateReportingAPI/Handlers/TransactionHandler.cs b/EstateReportingAPI/Handlers/TransactionHandler.cs index 328ce9f..7ec3e19 100644 --- a/EstateReportingAPI/Handlers/TransactionHandler.cs +++ b/EstateReportingAPI/Handlers/TransactionHandler.cs @@ -3,6 +3,8 @@ using MediatR; using Microsoft.AspNetCore.Mvc; using Shared.Results.Web; +using SimpleResults; +using TransactionProcessor.Database.Entities.Summary; using TodaysSales = EstateReportingAPI.Models.TodaysSales; namespace EstateReportingAPI.Handlers; @@ -205,4 +207,38 @@ public static async Task TodaysSalesByHour([FromHeader] Guid estateId, return ResponseFactory.FromResult(result, SuccessFactory); } -} \ No newline at end of file + public static async Task MerchantDailyPerformanceSummary([FromHeader] Guid estateId, + [FromBody] MerchantDailyPerformanceSummaryRequest request, + IMediator mediator, CancellationToken cancellationToken) + { + TransactionQueries.MerchantDailyPerformanceSummaryQuery query = new(estateId, new Models.MerchantDailyPerformanceSummaryRequest { + EndDate = request.EndDate, + MerchantReportingId = request.MerchantReportingId, + StartDate = request.StartDate + }); + Result result = await mediator.Send(query, cancellationToken); + + MerchantDailyPerformanceSummaryResponse SuccessFactory(Models.MerchantDailyPerformanceSummaryResponse r) => + new MerchantDailyPerformanceSummaryResponse + { + Metrics = r.Metrics?.Select(metric => new MetricItem + { + Title = metric.Title, + Value = metric.Value, + Description = metric.Description, + Category = metric.Category + }).ToList() ?? [], + DrillDownTransactions = r.DrillDownTransactions?.Select(transaction => new DrillDownTransaction + { + Reference = transaction.Reference, + Product = transaction.Product, + Status = transaction.Status, + Amount = transaction.Amount, + TransactionDateTime = transaction.TransactionDateTime + }).ToList() ?? [] + }; + + return ResponseFactory.FromResult(result, SuccessFactory); + } +} +