diff --git a/EstateReportingAPI.BusinessLogic/IReportingManager.cs b/EstateReportingAPI.BusinessLogic/IReportingManager.cs index df88b20..ab81654 100644 --- a/EstateReportingAPI.BusinessLogic/IReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/IReportingManager.cs @@ -11,6 +11,7 @@ public interface IReportingManager{ Task GetLastSettlement(Guid estateId, CancellationToken cancellationToken); Task> GetMerchants(Guid estateId, CancellationToken cancellationToken); Task GetMerchantsTransactionKpis(Guid estateId, CancellationToken cancellationToken); + Task> GetMerchantsByLastSale(Guid estateId, DateTime startDateTime, DateTime endDateTime, CancellationToken cancellationToken); Task> GetOperators(Guid estateId, CancellationToken cancellationToken); Task> GetResponseCodes(Guid estateId, CancellationToken cancellationToken); Task GetTodaysFailedSales(Guid estateId, DateTime comparisonDate, String responseCode, CancellationToken cancellationToken); diff --git a/EstateReportingAPI.BusinessLogic/ReportingManager.cs b/EstateReportingAPI.BusinessLogic/ReportingManager.cs index 588a9bf..8a8fab8 100644 --- a/EstateReportingAPI.BusinessLogic/ReportingManager.cs +++ b/EstateReportingAPI.BusinessLogic/ReportingManager.cs @@ -145,6 +145,55 @@ public async Task GetMerchantsTransactionKpis(Guid estateId, Cancel return response; } + public async Task> GetMerchantsByLastSale(Guid estateId, DateTime startDateTime, DateTime endDateTime, CancellationToken cancellationToken){ + EstateManagementGenericContext? context = await this.ContextFactory.GetContext(estateId, ReportingManager.ConnectionStringIdentifier, cancellationToken); + + List response = new(); + + var merchants = await context.Merchants + .Where(m => m.LastSaleDateTime >= startDateTime && m.LastSaleDateTime <= endDateTime) + .Select(m => new + { + MerchantReportingId = m.MerchantReportingId, + EstateReportingId = m.EstateReportingId, + Name = m.Name, + LastSaleDateTime = m.LastSaleDateTime, + LastSale = m.LastSaleDate, + CreatedDateTime = m.CreatedDateTime, + LastStatement = m.LastStatementGenerated, + MerchantId = m.MerchantId, + Reference = m.Reference, + AddressInfo = context.MerchantAddresses + .Where(ma => ma.MerchantReportingId == m.MerchantReportingId) + .OrderByDescending(ma => ma.CreatedDateTime) + .Select(ma => new + { + PostCode = ma.PostalCode, + Region = ma.Region, + Town = ma.Town, + // Add more properties as needed + }) + .FirstOrDefault() // Get the first matching MerchantAddress or null + }).ToListAsync(cancellationToken); + + merchants.ForEach(m => response.Add(new Merchant{ + LastSaleDateTime = m.LastSaleDateTime, + CreatedDateTime = m.CreatedDateTime, + EstateReportingId = m.EstateReportingId, + LastSale = m.LastSale, + LastStatement = m.LastStatement, + MerchantId = m.MerchantId, + MerchantReportingId = m.MerchantReportingId, + Name = m.Name, + Reference = m.Reference, + PostCode = m.AddressInfo?.PostCode, + Region = m.AddressInfo?.Region, + Town = m.AddressInfo?.Town + })); + + return response; + } + public async Task> GetResponseCodes(Guid estateId, CancellationToken cancellationToken){ EstateManagementGenericContext? context = await this.ContextFactory.GetContext(estateId, ReportingManager.ConnectionStringIdentifier, cancellationToken); List response = new List(); diff --git a/EstateReportingAPI.Client/EstateReportingApiClient.cs b/EstateReportingAPI.Client/EstateReportingApiClient.cs index b709918..fdbf5e9 100644 --- a/EstateReportingAPI.Client/EstateReportingApiClient.cs +++ b/EstateReportingAPI.Client/EstateReportingApiClient.cs @@ -251,6 +251,37 @@ public async Task GetProductPerformance(String accessToken, Guid es return response; } + public async Task> GetMerchantsByLastSaleDate(String accessToken, Guid estateId, DateTime startDate, DateTime endDate, CancellationToken cancellationToken){ + List response = new List(); + + String requestUri = this.BuildRequestUrl($"/api/facts/transactions/merchants/lastsale?startDate={startDate:yyyy-MM-dd HH:mm:ss}&enddate={endDate:yyyy-MM-dd HH:mm:ss}"); + + try + { + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Headers.Add("EstateId", estateId.ToString()); + + // Make the Http Call here + HttpResponseMessage httpResponse = await this.HttpClient.SendAsync(request, cancellationToken); + + // Process the response + String content = await this.HandleResponse(httpResponse, cancellationToken); + + // call was successful so now deserialise the body to the response object + response = JsonConvert.DeserializeObject>(content); + } + catch (Exception ex) + { + // An exception has occurred, add some additional information to the message + Exception exception = new Exception($"Error getting merchant by last sale date estate {estateId}.", ex); + + throw exception; + } + + return response; + } + public async Task GetOperatorPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List operatorIds, CancellationToken cancellationToken){ // Serialize the integer array into a comma-separated string string serializedArray = string.Join(",", operatorIds); diff --git a/EstateReportingAPI.Client/IEstateReportingApiClient.cs b/EstateReportingAPI.Client/IEstateReportingApiClient.cs index 8ed5174..511f0c0 100644 --- a/EstateReportingAPI.Client/IEstateReportingApiClient.cs +++ b/EstateReportingAPI.Client/IEstateReportingApiClient.cs @@ -12,29 +12,32 @@ public interface IEstateReportingApiClient{ Task> GetCalendarDates(String accessToken, Guid estateId, Int32 year, CancellationToken cancellationToken); Task> GetCalendarYears(String accessToken, Guid estateId, CancellationToken cancellationToken); Task> GetComparisonDates(String accessToken, Guid estateId, CancellationToken cancellationToken); - Task GetTodaysSales(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); - Task> GetTodaysSalesCountByHour(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); - Task> GetTodaysSalesValueByHour(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); - Task GetTodaysSettlement(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); + Task GetLastSettlement(String accessToken, Guid estateId, CancellationToken cancellationToken); Task GetMerchantKpi(String accessToken, Guid estateId, CancellationToken cancellationToken); + Task GetMerchantPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List merchantIds, CancellationToken cancellationToken); + Task> GetMerchants(String accessToken, Guid estateId, CancellationToken cancellationToken); - Task GetTodaysFailedSales(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, String responseCode, DateTime comparisonDate, CancellationToken cancellationToken); + Task> GetMerchantsByLastSaleDate(String accessToken, Guid estateId, DateTime startDate, DateTime endDate, CancellationToken cancellationToken); - Task> GetTopBottomOperatorData(String accessToken, Guid estateId, TopBottom topBottom, Int32 resultCount, CancellationToken cancellationToken); - Task> GetTopBottomMerchantData(String accessToken, Guid estateId, TopBottom topBottom, Int32 resultCount, CancellationToken cancellationToken); - Task> GetTopBottomProductData(String accessToken, Guid estateId, TopBottom topBottom, Int32 resultCount, CancellationToken cancellationToken); - Task> GetMerchants(String accessToken, Guid estateId, CancellationToken cancellationToken); + Task GetOperatorPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List operatorIds, CancellationToken cancellationToken); Task> GetOperators(String accessToken, Guid estateId, CancellationToken cancellationToken); - Task GetLastSettlement(String accessToken, Guid estateId,CancellationToken cancellationToken); + + Task GetProductPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List productIds, CancellationToken cancellationToken); Task> GetResponseCodes(String accessToken, Guid estateId, CancellationToken cancellationToken); - Task GetMerchantPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List merchantIds,CancellationToken cancellationToken); - Task GetProductPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List productIds, CancellationToken cancellationToken); + Task GetTodaysFailedSales(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, String responseCode, DateTime comparisonDate, CancellationToken cancellationToken); + Task GetTodaysSales(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); + Task> GetTodaysSalesCountByHour(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); + Task> GetTodaysSalesValueByHour(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); + Task GetTodaysSettlement(String accessToken, Guid estateId, Guid? merchantId, Guid? operatorId, DateTime comparisonDate, CancellationToken cancellationToken); + Task> GetTopBottomMerchantData(String accessToken, Guid estateId, TopBottom topBottom, Int32 resultCount, CancellationToken cancellationToken); - Task GetOperatorPerformance(String accessToken, Guid estateId, DateTime comparisonDate, List operatorIds, CancellationToken cancellationToken); + Task> GetTopBottomOperatorData(String accessToken, Guid estateId, TopBottom topBottom, Int32 resultCount, CancellationToken cancellationToken); + Task> GetTopBottomProductData(String accessToken, Guid estateId, TopBottom topBottom, Int32 resultCount, CancellationToken cancellationToken); Task> TransactionSearch(String accessToken, Guid estateId, TransactionSearchRequest searchRequest, Int32? page, Int32? pageSize, SortField? sortField, SortDirection? sortDirection, CancellationToken cancellationToken); + #endregion } } \ No newline at end of file diff --git a/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj b/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj index 9c94e01..90eef67 100644 --- a/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj +++ b/EstateReportingAPI.IntegrationTests/EstateReportingAPI.IntegrationTests.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - + Full false true diff --git a/EstateReportingAPI.IntegrationTests/FactTransactionsControllerTests.cs b/EstateReportingAPI.IntegrationTests/FactTransactionsControllerTests.cs index c8e3c4f..9a4f7e4 100644 --- a/EstateReportingAPI.IntegrationTests/FactTransactionsControllerTests.cs +++ b/EstateReportingAPI.IntegrationTests/FactTransactionsControllerTests.cs @@ -5,9 +5,11 @@ using EstateManagement.Database.Contexts; using EstateManagement.Database.Entities; using EstateReportingAPI.DataTransferObjects; +using Microsoft.OpenApi.Services; using Newtonsoft.Json; using Shouldly; using Xunit; +using Merchant = DataTrasferObjects.Merchant; using SortDirection = DataTransferObjects.SortDirection; public class FactTransactionsControllerTests : ControllerTestsBase @@ -1926,5 +1928,105 @@ public async Task FactTransactionsController_TransactionSearch_SortingTest_Trans searchResult[1].TransactionAmount.ShouldBe(200); searchResult[2].TransactionAmount.ShouldBe(100); } + + [Theory] + [InlineData(ClientType.Api)] + [InlineData(ClientType.Direct)] + public async Task FactTransactionsControllerController_GetMerchantsByLastDaleDate_MerchantsReturned(ClientType clientType) + { + DateTime todaysDateTime = DateTime.Now; + + await ClearStandingData(); + + // Last Hour + await helper.AddMerchant("Test Estate", "Merchant 1", todaysDateTime.AddMinutes(-10)); + await helper.AddMerchant("Test Estate", "Merchant 2", todaysDateTime.AddMinutes(-10)); + await helper.AddMerchant("Test Estate", "Merchant 3", todaysDateTime.AddMinutes(-10)); + await helper.AddMerchant("Test Estate", "Merchant 4", todaysDateTime.AddMinutes(-10)); + + // Yesterday + await helper.AddMerchant("Test Estate", "Merchant 5", todaysDateTime.AddDays(-1)); + await helper.AddMerchant("Test Estate", "Merchant 6", todaysDateTime.AddDays(-1)); + await helper.AddMerchant("Test Estate", "Merchant 7", todaysDateTime.AddDays(-1)); + await helper.AddMerchant("Test Estate", "Merchant 8", todaysDateTime.AddDays(-1)); + await helper.AddMerchant("Test Estate", "Merchant 9", todaysDateTime.AddDays(-1)); + await helper.AddMerchant("Test Estate", "Merchant 10", todaysDateTime.AddDays(-1)); + + // 5 days ago + await helper.AddMerchant("Test Estate", "Merchant 11", todaysDateTime.AddDays(-5)); + await helper.AddMerchant("Test Estate", "Merchant 12", todaysDateTime.AddDays(-5)); + await helper.AddMerchant("Test Estate", "Merchant 13", todaysDateTime.AddDays(-5)); + + // 10 Days Ago + await helper.AddMerchant("Test Estate", "Merchant 14", todaysDateTime.AddDays(-10)); + await helper.AddMerchant("Test Estate", "Merchant 15", todaysDateTime.AddDays(-10)); + await helper.AddMerchant("Test Estate", "Merchant 16", todaysDateTime.AddDays(-10)); + await helper.AddMerchant("Test Estate", "Merchant 17", todaysDateTime.AddDays(-10)); + await helper.AddMerchant("Test Estate", "Merchant 18", todaysDateTime.AddDays(-10)); + + DateTime startDate = DateTime.Now; + DateTime endDate = DateTime.Now; + + Func?>> asyncFunction = async () => + { + List? result = clientType switch + { + ClientType.Api => await this.ApiClient.GetMerchantsByLastSaleDate(String.Empty, Guid.NewGuid(), startDate,endDate, CancellationToken.None), + _ => await CreateAndSendHttpRequestMessage>($"api/facts/transactions/merchants/lastsale?startDate={startDate:yyyy-MM-dd HH:mm:ss}&enddate={endDate:yyyy-MM-dd HH:mm:ss}", CancellationToken.None) + }; + return result; + }; + // Test 1 - sale in last hour + startDate = DateTime.Now.AddHours(-1); + endDate = DateTime.Now; + List? searchResult = await ExecuteAsyncFunction(asyncFunction); + searchResult.ShouldNotBeNull(); + searchResult.Count.ShouldBe(4); + searchResult.SingleOrDefault(s => s.Name == "Merchant 1").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 2").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 3").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 4").ShouldNotBeNull(); + + // Test 2 - sale in last day but over an hour ago + startDate = DateTime.Now.Date.AddDays(-1); + endDate = DateTime.Now.AddHours(-1); + searchResult = await ExecuteAsyncFunction(asyncFunction); + searchResult.ShouldNotBeNull(); + searchResult.Count.ShouldBe(6); + searchResult.SingleOrDefault(s => s.Name == "Merchant 5").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 6").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 7").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 8").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 9").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 10").ShouldNotBeNull(); + + // Test 3 - sale in last 7 days but non yesterday + startDate = DateTime.Now.Date.AddDays(-7); + endDate = DateTime.Now.Date.AddDays(-1); + searchResult = await ExecuteAsyncFunction(asyncFunction); + searchResult.ShouldNotBeNull(); + searchResult.Count.ShouldBe(3); + searchResult.SingleOrDefault(s => s.Name == "Merchant 11").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 12").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 13").ShouldNotBeNull(); + + // Test 4 - sale more than 7 days ago + startDate = DateTime.Now.Date.AddYears(-1); + endDate = DateTime.Now.Date.AddDays(-7); + searchResult = await ExecuteAsyncFunction(asyncFunction); + searchResult.ShouldNotBeNull(); + searchResult.Count.ShouldBe(5); + searchResult.SingleOrDefault(s => s.Name == "Merchant 14").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 15").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 16").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 17").ShouldNotBeNull(); + searchResult.SingleOrDefault(s => s.Name == "Merchant 18").ShouldNotBeNull(); + + + + + + } + } diff --git a/EstateReportingAPI/Controllers/DimensionsController.cs b/EstateReportingAPI/Controllers/DimensionsController.cs index 9661cd9..1bed494 100644 --- a/EstateReportingAPI/Controllers/DimensionsController.cs +++ b/EstateReportingAPI/Controllers/DimensionsController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using System.Diagnostics.CodeAnalysis; using BusinessLogic; + using IdentityModel; [ExcludeFromCodeCoverage] [Route(DimensionsController.ControllerRoute)] diff --git a/EstateReportingAPI/Controllers/FactTransactionsController.cs b/EstateReportingAPI/Controllers/FactTransactionsController.cs index 2c882ea..ae8001d 100644 --- a/EstateReportingAPI/Controllers/FactTransactionsController.cs +++ b/EstateReportingAPI/Controllers/FactTransactionsController.cs @@ -99,6 +99,32 @@ public async Task TodaysSalesValueByHour([FromHeader] Guid estate return this.Ok(response); } + [HttpGet] + [Route("merchants/lastsale")] + public async Task GetMerchantsByLastSale([FromHeader] Guid estateId, [FromQuery] DateTime startDate, [FromQuery] DateTime endDate, CancellationToken cancellationToken){ + List merchants = await this.ReportingManager.GetMerchantsByLastSale(estateId,startDate, endDate, cancellationToken); + + List response = new List(); + + merchants.ForEach(m => response.Add(new Merchant + { + MerchantReportingId = m.MerchantReportingId, + MerchantId = m.MerchantId, + EstateReportingId = m.EstateReportingId, + Name = m.Name, + LastSaleDateTime = m.LastSaleDateTime, + CreatedDateTime = m.CreatedDateTime, + LastSale = m.LastSale, + LastStatement = m.LastStatement, + PostCode = m.PostCode, + Reference = m.Reference, + Region = m.Region, + Town = m.Town, + })); + + return this.Ok(response.OrderBy(m => m.Name)); + } + [HttpGet] [Route("merchantkpis")] public async Task GetMerchantsTransactionKpis([FromHeader] Guid estateId, CancellationToken cancellationToken){