From 1bfb47afa4fb3397f667bec822894e4ac11cbd56 Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Fri, 10 Apr 2026 14:48:48 +0100 Subject: [PATCH 1/2] Refactor merchant opening hours to use dedicated API Decouple opening hours from MerchantModel and fetch via new API endpoint and service methods. Add DTOs and business logic for opening hours retrieval. Update Blazor components and tests to use the new model. Improve merchant schedule save logic and error handling. Remove legacy code and update dependencies. --- .../Merchants/MerchantSchedulePageTests.cs | 9 +- .../Pages/Merchants/MerchantsEditPageTests.cs | 17 ++- .../Pages/Merchants/MerchantsViewPageTests.cs | 40 +++-- .../Components/Pages/Merchants/Edit.razor.cs | 56 +++---- .../Pages/Merchants/Schedule.razor.cs | 15 +- .../Components/Pages/Merchants/View.razor.cs | 32 +++- .../Factories/ModelFactory.cs | 29 ++-- .../Models/MerchantModels.cs | 1 - .../UIServices/MerchantUIService.cs | 27 +++- .../DataTransferObjects/Merchant.cs | 32 +++- .../BackendAPI/EstateReportingApiClient.cs | 72 ++++++++- .../Client/APIModelFactory.cs | 141 ++++++------------ .../Client/MerchantMethods.cs | 51 ++++++- .../EstateManagementUI.BusinessLogic.csproj | 2 +- .../Models/MerchantModels.cs | 2 - .../RequestHandlers/MerchantRequestHandler.cs | 14 +- .../Requests/MerchantCommands.cs | 9 +- .../Requests/MerchantQueries.cs | 1 + 18 files changed, 356 insertions(+), 194 deletions(-) diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs index fbd9d1e1..2c2a5cc9 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs @@ -111,7 +111,7 @@ public void MerchantSchedule_SaveSelectedYear_InvalidNovemberDate_ShowsErrorAndD SetupPageData(merchantId, currentYear, new MerchantModels.MerchantScheduleModel { Year = currentYear, Months = [] }); SetupSchedule(futureYear, new MerchantModels.MerchantScheduleModel { Year = futureYear, Months = [] }); - + var cut = RenderComponent(parameters => parameters.Add(p => p.MerchantId, merchantId)); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); @@ -120,7 +120,7 @@ public void MerchantSchedule_SaveSelectedYear_InvalidNovemberDate_ShowsErrorAndD cut.Find("#month-11-closed-days").Change("31"); cut.Find("#saveScheduleButton").Click(); - cut.Markup.ShouldContain($"Only days between 1 and 30 can be supplied for November {futureYear}."); + cut.Markup.ShouldContain($"Invalid closed days for November."); this.MerchantUIService.Verify(m => m.SaveMerchantSchedule(It.IsAny(), It.IsAny(), merchantId, It.IsAny()), Times.Never); } @@ -165,7 +165,8 @@ public void MerchantSchedule_SaveSelectedYear_NonLeapYearFebruary_Rejects29th() SetupPageData(merchantId, currentYear, new MerchantModels.MerchantScheduleModel { Year = currentYear, Months = [] }); SetupSchedule(nonLeapYear, new MerchantModels.MerchantScheduleModel { Year = nonLeapYear, Months = [] }); - + this.MerchantUIService.Setup(m => m.SaveMerchantSchedule(It.IsAny(), It.IsAny(), merchantId, + It.IsAny())).ReturnsAsync(Result.Failure($"Only days between 1 and 28 can be supplied for February {nonLeapYear}.")); var cut = RenderComponent(parameters => parameters.Add(p => p.MerchantId, merchantId)); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); @@ -175,8 +176,6 @@ public void MerchantSchedule_SaveSelectedYear_NonLeapYearFebruary_Rejects29th() cut.Find("#saveScheduleButton").Click(); cut.Markup.ShouldContain($"Only days between 1 and 28 can be supplied for February {nonLeapYear}."); - this.MerchantUIService.Verify(m => m.SaveMerchantSchedule(It.IsAny(), It.IsAny(), merchantId, - It.IsAny()), Times.Never); } [Fact] diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs index a7052573..db5d1169 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsEditPageTests.cs @@ -1217,7 +1217,18 @@ private void SetupSuccessfulDataLoad(Guid merchantId, Balance = 1000.00m, AvailableBalance = 500.00m }; - + + var openingHours = new MerchantModels.MerchantOpeningHoursModel + { + Monday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Thursday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Friday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Saturday = new MerchantModels.DayOpeningHoursModel { Opening = "0900", Closing = "1600" }, + Sunday = new MerchantModels.DayOpeningHoursModel { Opening = "1000", Closing = "1500" } + }; + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) .ReturnsAsync(Result.Success(merchant)); @@ -1229,7 +1240,9 @@ private void SetupSuccessfulDataLoad(Guid merchantId, this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), merchantId)) .ReturnsAsync(Result.Success(assignedDevices ?? new List())); - + this.MerchantUIService.Setup(m => m.GetMerchantOpeningHours(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(openingHours)); + this.OperatorUIService.Setup(o => o.GetOperatorsForDropDown(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Success(availableOperators ?? new List())); diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs index 49866609..675adfd4 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsViewPageTests.cs @@ -32,6 +32,7 @@ public void MerchantsView_InitialState_ShowsLoadingIndicator() this.MerchantUIService.Setup(m => m.GetMerchantOperators(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantContracts(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); + this.MerchantUIService.Setup(m => m.GetMerchantOpeningHours(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new MerchantModels.MerchantOpeningHoursModel())); // Act var cut = RenderComponent(parameters => parameters @@ -58,7 +59,7 @@ public void MerchantsView_WithMerchant_DisplaysMerchantName() this.MerchantUIService.Setup(m => m.GetMerchantOperators(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantContracts(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); - + this.MerchantUIService.Setup(m => m.GetMerchantOpeningHours(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new MerchantModels.MerchantOpeningHoursModel())); // Act var cut = RenderComponent(parameters => parameters @@ -87,6 +88,7 @@ public void MerchantsView_HasCorrectPageTitle() this.MerchantUIService.Setup(m => m.GetMerchantOperators(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantContracts(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); + this.MerchantUIService.Setup(m => m.GetMerchantOpeningHours(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new MerchantModels.MerchantOpeningHoursModel())); // Act var cut = RenderComponent(parameters => parameters @@ -114,7 +116,7 @@ public void MerchantsView_HasBackButton() this.MerchantUIService.Setup(m => m.GetMerchantOperators(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantContracts(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new List())); - + this.MerchantUIService.Setup(m => m.GetMerchantOpeningHours(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(Result.Success(new MerchantModels.MerchantOpeningHoursModel())); // Act var cut = RenderComponent(parameters => parameters @@ -506,27 +508,10 @@ public void MerchantsView_DisplaysContactDetails_WhenPresent() [Fact] public void MerchantsView_DisplaysOpeningHours_WhenPresent() { - var merchant = new MerchantModels.MerchantModel - { - MerchantId = Guid.NewGuid(), - MerchantName = "Test Merchant", - MerchantReference = "REF001", - OpeningHours = new MerchantModels.MerchantOpeningHoursModel - { - Monday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, - Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, - Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, - Thursday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, - Friday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, - Saturday = new MerchantModels.DayOpeningHoursModel { Opening = "0900", Closing = "1600" }, - Sunday = new MerchantModels.DayOpeningHoursModel { Opening = "1000", Closing = "1500" } - } - }; - - SetupSuccessfulDataLoadWithMerchant(merchant); + SetupSuccessfulDataLoad(); var cut = RenderComponent(parameters => parameters - .Add(p => p.MerchantId, merchant.MerchantId)); + .Add(p => p.MerchantId, Guid.NewGuid())); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); IRefreshableElementCollection buttons = cut.FindAll("button"); @@ -595,6 +580,17 @@ private void SetupSuccessfulDataLoad( MerchantReference = "REF001" }; + var openingHours = new MerchantModels.MerchantOpeningHoursModel + { + Monday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Thursday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Friday = new MerchantModels.DayOpeningHoursModel { Opening = "0800", Closing = "1700" }, + Saturday = new MerchantModels.DayOpeningHoursModel { Opening = "0900", Closing = "1600" }, + Sunday = new MerchantModels.DayOpeningHoursModel { Opening = "1000", Closing = "1500" } + }; + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Success(merchant)); this.MerchantUIService.Setup(m => m.GetMerchantOperators(It.IsAny(), It.IsAny(), It.IsAny())) @@ -603,6 +599,8 @@ private void SetupSuccessfulDataLoad( .ReturnsAsync(Result.Success(contracts ?? new List())); this.MerchantUIService.Setup(m => m.GetMerchantDevices(It.IsAny(), It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Success(devices ?? new List())); + this.MerchantUIService.Setup(m => m.GetMerchantOpeningHours(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(openingHours ?? new MerchantModels.MerchantOpeningHoursModel())); } private void SetupSuccessfulDataLoadWithMerchant(MerchantModels.MerchantModel merchant) diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs index d1ccc5ba..44f9bf73 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Edit.razor.cs @@ -98,22 +98,13 @@ private async Task LoadMerchant() { ContactEmailAddress = merchant.ContactEmailAddress, ContactPhoneNumber = merchant.ContactPhoneNumber, }; - merchantOpeningHoursModel = new MerchantModels.MerchantOpeningHoursModel - { - Sunday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Sunday.Opening, Closing = merchant.OpeningHours.Sunday.Closing }, - Monday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Monday.Opening, Closing = merchant.OpeningHours.Monday.Closing }, - Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Tuesday.Opening, Closing = merchant.OpeningHours.Tuesday.Closing }, - Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Wednesday.Opening, Closing = merchant.OpeningHours.Wednesday.Closing }, - Thursday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Thursday.Opening, Closing = merchant.OpeningHours.Thursday.Closing }, - Friday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Friday.Opening, Closing = merchant.OpeningHours.Friday.Closing }, - Saturday = new MerchantModels.DayOpeningHoursModel { Opening = merchant.OpeningHours.Saturday.Opening, Closing = merchant.OpeningHours.Saturday.Closing } - }; - + var operatorsResultTask = this.MerchantUiService.GetMerchantOperators(correlationId, estateId, MerchantId); var contractsResultTask = this.MerchantUiService.GetMerchantContracts(correlationId, estateId, MerchantId); var devicesResultTask = this.MerchantUiService.GetMerchantDevices(correlationId, estateId, MerchantId); + var openingHoursResultTask = this.MerchantUiService.GetMerchantOpeningHours(correlationId, estateId, MerchantId); - await Task.WhenAll(operatorsResultTask, contractsResultTask, devicesResultTask); + await Task.WhenAll(operatorsResultTask, contractsResultTask, devicesResultTask, openingHoursResultTask); if (operatorsResultTask.Result.IsFailed) return ResultHelpers.CreateFailure(operatorsResultTask.Result); @@ -124,11 +115,26 @@ private async Task LoadMerchant() { if (devicesResultTask.Result.IsFailed) return ResultHelpers.CreateFailure(devicesResultTask.Result); + if (openingHoursResultTask.Result.IsFailed) + return ResultHelpers.CreateFailure(openingHoursResultTask.Result); + assignedOperators = operatorsResultTask.Result.Data; assignedContracts = contractsResultTask.Result.Data; this.assignedDevices = devicesResultTask.Result.Data; - Task>> operatorsDropDownTask = this.OperatorUiService.GetOperatorsForDropDown(correlationId, estateId); + merchantOpeningHoursModel = new MerchantModels.MerchantOpeningHoursModel + { + Sunday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Sunday.Opening, Closing = openingHoursResultTask.Result.Data.Sunday.Closing }, + Monday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Monday.Opening, Closing = openingHoursResultTask.Result.Data.Monday.Closing }, + Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Tuesday.Opening, Closing = openingHoursResultTask.Result.Data.Tuesday.Closing }, + Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Wednesday.Opening, Closing = openingHoursResultTask.Result.Data.Wednesday.Closing }, + Thursday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Thursday.Opening, Closing = openingHoursResultTask.Result.Data.Thursday.Closing }, + Friday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Friday.Opening, Closing = openingHoursResultTask.Result.Data.Friday.Closing }, + Saturday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Saturday.Opening, Closing = openingHoursResultTask.Result.Data.Saturday.Closing } + }; + + + Task>> operatorsDropDownTask = this.OperatorUiService.GetOperatorsForDropDown(correlationId, estateId); Task>> contractsDropDownTask = this.ContractUiService.GetContractsForDropDown(correlationId, estateId); await Task.WhenAll(operatorsDropDownTask, contractsDropDownTask); @@ -216,18 +222,18 @@ private async Task SaveOpeningHours() { if (result.IsSuccess) { successMessage = "Merchant opening hours updated successfully"; - if (merchant != null) { - merchant.OpeningHours = new MerchantModels.MerchantOpeningHoursModel - { - Sunday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Sunday.Opening, Closing = merchantOpeningHoursModel.Sunday.Closing }, - Monday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Monday.Opening, Closing = merchantOpeningHoursModel.Monday.Closing }, - Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Tuesday.Opening, Closing = merchantOpeningHoursModel.Tuesday.Closing }, - Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Wednesday.Opening, Closing = merchantOpeningHoursModel.Wednesday.Closing }, - Thursday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Thursday.Opening, Closing = merchantOpeningHoursModel.Thursday.Closing }, - Friday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Friday.Opening, Closing = merchantOpeningHoursModel.Friday.Closing }, - Saturday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Saturday.Opening, Closing = merchantOpeningHoursModel.Saturday.Closing } - }; - } + //if (merchant != null) { + //merchant.OpeningHours = new MerchantModels.MerchantOpeningHoursModel + //{ + // Sunday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Sunday.Opening, Closing = merchantOpeningHoursModel.Sunday.Closing }, + // Monday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Monday.Opening, Closing = merchantOpeningHoursModel.Monday.Closing }, + // Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Tuesday.Opening, Closing = merchantOpeningHoursModel.Tuesday.Closing }, + // Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Wednesday.Opening, Closing = merchantOpeningHoursModel.Wednesday.Closing }, + // Thursday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Thursday.Opening, Closing = merchantOpeningHoursModel.Thursday.Closing }, + // Friday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Friday.Opening, Closing = merchantOpeningHoursModel.Friday.Closing }, + // Saturday = new MerchantModels.DayOpeningHoursModel { Opening = merchantOpeningHoursModel.Saturday.Opening, Closing = merchantOpeningHoursModel.Saturday.Closing } + //}; + //} } else { errorMessage = "Failed to update merchant opening hours"; diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Schedule.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Schedule.razor.cs index 3d4c6a6c..aaf4af02 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Schedule.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Schedule.razor.cs @@ -15,7 +15,7 @@ public partial class Schedule public Guid MerchantId { get; set; } [Parameter] - [SupplyParameterFromQuery(Name = "readOnly")] + //[SupplyParameterFromQuery(Name = "readOnly")] public Boolean ReadOnly { get; set; } private readonly DateTime today = DateTime.Today; @@ -149,7 +149,12 @@ private async Task SaveScheduleAsync() Result scheduleResult = this.BuildScheduleToSave(); if (scheduleResult.IsFailed) { - this.errorMessage = scheduleResult.Errors.SingleOrDefault() ?? "Invalid schedule."; + this.errorMessage = scheduleResult switch + { + _ when scheduleResult.Errors.Any() => scheduleResult.Errors.First(), + _ when String.IsNullOrEmpty(scheduleResult.Message) == false => scheduleResult.Message, + _ => "Invalid schedule." + }; return; } @@ -158,7 +163,11 @@ private async Task SaveScheduleAsync() Result result = await this.MerchantUIService.SaveMerchantSchedule(correlationId, estateId, this.MerchantId, scheduleResult.Data); if (result.IsFailed) { - this.errorMessage = result.Errors.SingleOrDefault() ?? "Failed to save merchant schedule."; + this.errorMessage = result switch { + _ when result.Errors.Any() => result.Errors.First(), + _ when String.IsNullOrEmpty(result.Message) == false => result.Message, + _ => "Failed to save merchant schedule." + }; return; } diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor.cs index 021543af..ae86b216 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/View.razor.cs @@ -23,6 +23,7 @@ public partial class View private List assignedOperators = new(); private List assignedContracts = new(); private List assignedDevices = new(); + private MerchantModels.MerchantOpeningHoursModel merchantOpeningHoursModel = new(); // Settlement transaction history data private List? settlementTransactions; @@ -67,8 +68,9 @@ private async Task LoadMerchant() var operatorsResultTask = this.MerchantUIService.GetMerchantOperators(correlationId, estateId, MerchantId); var contractsResultTask = this.MerchantUIService.GetMerchantContracts(correlationId, estateId, MerchantId); var devicesResultTask = this.MerchantUIService.GetMerchantDevices(correlationId, estateId, MerchantId); + var openingHoursResultTask = this.MerchantUIService.GetMerchantOpeningHours(correlationId, estateId, MerchantId); - await Task.WhenAll(operatorsResultTask, contractsResultTask, devicesResultTask); + await Task.WhenAll(operatorsResultTask, contractsResultTask, devicesResultTask, openingHoursResultTask); if (operatorsResultTask.Result.IsFailed) return ResultHelpers.CreateFailure(operatorsResultTask.Result); @@ -78,11 +80,25 @@ private async Task LoadMerchant() if (devicesResultTask.Result.IsFailed) return ResultHelpers.CreateFailure(devicesResultTask.Result); + if (openingHoursResultTask.Result.IsFailed) + return ResultHelpers.CreateFailure(openingHoursResultTask.Result); assignedOperators = operatorsResultTask.Result.Data; assignedContracts = contractsResultTask.Result.Data; this.assignedDevices = devicesResultTask.Result.Data; + merchantOpeningHoursModel = new MerchantModels.MerchantOpeningHoursModel + { + Sunday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Sunday.Opening, Closing = openingHoursResultTask.Result.Data.Sunday.Closing }, + Monday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Monday.Opening, Closing = openingHoursResultTask.Result.Data.Monday.Closing }, + Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Tuesday.Opening, Closing = openingHoursResultTask.Result.Data.Tuesday.Closing }, + Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Wednesday.Opening, Closing = openingHoursResultTask.Result.Data.Wednesday.Closing }, + Thursday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Thursday.Opening, Closing = openingHoursResultTask.Result.Data.Thursday.Closing }, + Friday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Friday.Opening, Closing = openingHoursResultTask.Result.Data.Friday.Closing }, + Saturday = new MerchantModels.DayOpeningHoursModel { Opening = openingHoursResultTask.Result.Data.Saturday.Opening, Closing = openingHoursResultTask.Result.Data.Saturday.Closing } + }; + + return Result.Success(); } finally @@ -211,13 +227,13 @@ private string GetTabClass(string tab) private IReadOnlyList GetOpeningHoursRows() => [ - new("Monday", merchant?.OpeningHours.Monday ?? new MerchantModels.DayOpeningHoursModel()), - new("Tuesday", merchant?.OpeningHours.Tuesday ?? new MerchantModels.DayOpeningHoursModel()), - new("Wednesday", merchant?.OpeningHours.Wednesday ?? new MerchantModels.DayOpeningHoursModel()), - new("Thursday", merchant?.OpeningHours.Thursday ?? new MerchantModels.DayOpeningHoursModel()), - new("Friday", merchant?.OpeningHours.Friday ?? new MerchantModels.DayOpeningHoursModel()), - new("Saturday", merchant?.OpeningHours.Saturday ?? new MerchantModels.DayOpeningHoursModel()), - new("Sunday", merchant?.OpeningHours.Sunday ?? new MerchantModels.DayOpeningHoursModel()) + new("Monday", merchantOpeningHoursModel.Monday ?? new MerchantModels.DayOpeningHoursModel()), + new("Tuesday", merchantOpeningHoursModel.Tuesday ?? new MerchantModels.DayOpeningHoursModel()), + new("Wednesday", merchantOpeningHoursModel.Wednesday ?? new MerchantModels.DayOpeningHoursModel()), + new("Thursday", merchantOpeningHoursModel.Thursday ?? new MerchantModels.DayOpeningHoursModel()), + new("Friday", merchantOpeningHoursModel.Friday ?? new MerchantModels.DayOpeningHoursModel()), + new("Saturday", merchantOpeningHoursModel.Saturday ?? new MerchantModels.DayOpeningHoursModel()), + new("Sunday", merchantOpeningHoursModel.Sunday ?? new MerchantModels.DayOpeningHoursModel()) ]; private void ViewSchedule() => NavigationManager.NavigateToMerchantSchedule(this.MerchantId, readOnly: true); diff --git a/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs b/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs index daa02221..626428fe 100644 --- a/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs +++ b/EstateManagementUI.BlazorServer/Factories/ModelFactory.cs @@ -77,16 +77,6 @@ public static MerchantModel ConvertFrom(BusinessLogic.Models.MerchantModels.Merc Balance = model.Balance, AvailableBalance = model.AvailableBalance, SettlementSchedule = model.SettlementSchedule, - OpeningHours = new MerchantModels.MerchantOpeningHoursModel - { - Sunday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Sunday.Opening, Closing = model.OpeningHours.Sunday.Closing }, - Monday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Monday.Opening, Closing = model.OpeningHours.Monday.Closing }, - Tuesday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Tuesday.Opening, Closing = model.OpeningHours.Tuesday.Closing }, - Wednesday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Wednesday.Opening, Closing = model.OpeningHours.Wednesday.Closing }, - Thursday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Thursday.Opening, Closing = model.OpeningHours.Thursday.Closing }, - Friday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Friday.Opening, Closing = model.OpeningHours.Friday.Closing }, - Saturday = new MerchantModels.DayOpeningHoursModel { Opening = model.OpeningHours.Saturday.Opening, Closing = model.OpeningHours.Saturday.Closing } - }, AddressLine1 = model.AddressLine1, AddressLine2 = model.AddressLine2, Town = model.Town, @@ -612,4 +602,23 @@ public static ProductPerformanceResponse ConvertFrom(BusinessLogic.Models.Transa return todaysSalesByHourModels; } + + public static MerchantModels.MerchantOpeningHoursModel ConvertFrom(BusinessLogic.Models.MerchantModels.MerchantOpeningHoursModel resultData) { + MerchantModels.MerchantOpeningHoursModel model = new(); + model.Sunday = ConvertFrom(resultData.Sunday); + model.Monday = ConvertFrom(resultData.Monday); + model.Tuesday = ConvertFrom(resultData.Tuesday); + model.Wednesday = ConvertFrom(resultData.Wednesday); + model.Thursday = ConvertFrom(resultData.Thursday); + model.Friday = ConvertFrom(resultData.Friday); + model.Saturday = ConvertFrom(resultData.Saturday); + return model; + } + + private static MerchantModels.DayOpeningHoursModel ConvertFrom(BusinessLogic.Models.MerchantModels.DayOpeningHoursModel resultData) { + MerchantModels.DayOpeningHoursModel model = new(); + model.Opening = resultData.Opening; + model.Closing = resultData.Closing; + return model; + } } diff --git a/EstateManagementUI.BlazorServer/Models/MerchantModels.cs b/EstateManagementUI.BlazorServer/Models/MerchantModels.cs index 6cb21c84..7c62897e 100644 --- a/EstateManagementUI.BlazorServer/Models/MerchantModels.cs +++ b/EstateManagementUI.BlazorServer/Models/MerchantModels.cs @@ -21,7 +21,6 @@ public class MerchantModel public decimal? Balance { get; set; } public decimal? AvailableBalance { get; set; } public string? SettlementSchedule { get; set; } - public MerchantOpeningHoursModel OpeningHours { get; set; } = new(); public Guid AddressId { get; set; } public string? AddressLine1 { get; set; } public string? AddressLine2 { get; set; } diff --git a/EstateManagementUI.BlazorServer/UIServices/MerchantUIService.cs b/EstateManagementUI.BlazorServer/UIServices/MerchantUIService.cs index c5ca563d..ceb010cc 100644 --- a/EstateManagementUI.BlazorServer/UIServices/MerchantUIService.cs +++ b/EstateManagementUI.BlazorServer/UIServices/MerchantUIService.cs @@ -98,6 +98,8 @@ Task MakeMerchantDeposit(CorrelationId correlationId, Task> GetMerchantSchedule(CorrelationId correlationId, Guid estateId, Guid merchantId, Int32 year); Task SaveMerchantSchedule(CorrelationId correlationId, Guid estateId, Guid merchantId, MerchantModels.MerchantScheduleModel scheduleModel); + + Task> GetMerchantOpeningHours(CorrelationId correlationId, Guid estateId, Guid merchantId); } public class MerchantUIService : IMerchantUIService { @@ -344,21 +346,44 @@ public async Task SaveMerchantSchedule(CorrelationId correlationId, Guid estateId, Guid merchantId, MerchantModels.MerchantScheduleModel scheduleModel) { + Result loadResult = await this.GetMerchantSchedule(correlationId, estateId, merchantId, scheduleModel.Year); + if (loadResult.IsFailed && loadResult.Status != ResultStatus.NotFound) + return ResultHelpers.CreateFailure(loadResult); + BusinessLogic.Models.MerchantModels.MerchantScheduleModel businessLogicModel = new() { Year = scheduleModel.Year, Months = scheduleModel.Months .OrderBy(month => month.Month) + .Where(month => month.ClosedDays.Any()) .Select(month => new BusinessLogic.Models.MerchantModels.MerchantScheduleMonthModel { Month = month.Month, ClosedDays = month.ClosedDays.OrderBy(day => day).ToList() }).ToList() }; - MerchantCommands.CreateMerchantScheduleCommand command = new(correlationId, estateId, merchantId, businessLogicModel); + IRequest command; + if (loadResult.Status == ResultStatus.NotFound) { + command = new MerchantCommands.CreateMerchantScheduleCommand(correlationId, estateId, merchantId, businessLogicModel); + } + else { + command = new MerchantCommands.UpdateMerchantScheduleCommand(correlationId, estateId, merchantId, businessLogicModel); + } + var result = await this.Mediator.Send(command); if (result.IsFailed) return ResultHelpers.CreateFailure(result); return Result.Success(); } + + public async Task> GetMerchantOpeningHours(CorrelationId correlationId, + Guid estateId, + Guid merchantId) { + MerchantQueries.GetMerchantOpeningHoursQuery query = new(correlationId, estateId, merchantId); + var result = await this.Mediator.Send(query); + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + var openingHours = ModelFactory.ConvertFrom(result.Data); + return Result.Success(openingHours); + } } diff --git a/EstateManagmentUI.BusinessLogic/BackendAPI/DataTransferObjects/Merchant.cs b/EstateManagmentUI.BusinessLogic/BackendAPI/DataTransferObjects/Merchant.cs index 94bf2202..5531bc37 100644 --- a/EstateManagmentUI.BusinessLogic/BackendAPI/DataTransferObjects/Merchant.cs +++ b/EstateManagmentUI.BusinessLogic/BackendAPI/DataTransferObjects/Merchant.cs @@ -43,7 +43,35 @@ public class Merchant public String ContactEmail { get; set; } [JsonProperty("contact_phone")] public String ContactPhone { get; set; } +} - [JsonProperty("opening_hours")] - public Dictionary OpeningHours { get; set; } + +public class MerchantOpeningHour +{ + [JsonProperty("merchant_id")] + public Guid MerchantId { get; set; } + [JsonProperty("day_of_week")] + public DayOfWeek DayOfWeek { get; set; } + [JsonProperty("opening_time")] + public String OpeningTime { get; set; } + [JsonProperty("closing_time")] + public String ClosingTime { get; set; } +} + +public class MerchantScheduleResponse +{ + [JsonProperty("year")] + public int Year { get; set; } + + [JsonProperty("months")] + public List Months { get; set; } +} + +public class MerchantScheduleMonthResponse +{ + [JsonProperty("month")] + public int Month { get; set; } + + [JsonProperty("closed_days")] + public List ClosedDays { get; set; } } \ No newline at end of file diff --git a/EstateManagmentUI.BusinessLogic/BackendAPI/EstateReportingApiClient.cs b/EstateManagmentUI.BusinessLogic/BackendAPI/EstateReportingApiClient.cs index 976baae3..dfaac52b 100644 --- a/EstateManagmentUI.BusinessLogic/BackendAPI/EstateReportingApiClient.cs +++ b/EstateManagmentUI.BusinessLogic/BackendAPI/EstateReportingApiClient.cs @@ -1,11 +1,13 @@ -using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; +using Azure.Core; +using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; +using Newtonsoft.Json; using Shared.Results; using SimpleResults; using System; using System.Collections.Generic; using System.Text; -using Newtonsoft.Json; using TransactionProcessor.DataTransferObjects.Responses.Merchant; +using MerchantScheduleResponse = EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects.MerchantScheduleResponse; namespace EstateManagementUI.BusinessLogic.BackendAPI { @@ -101,6 +103,17 @@ Task> GetTodaysSettlement(String accessToken, Guid estateId, DateTime comparisonDate, CancellationToken cancellationToken); + + Task> GetMerchantSchedule(String accessToken, + Guid estateId, + Guid merchantId, + Int32 year, + CancellationToken cancellationToken); + + Task>> GetMerchantOpeningHours(String accessToken, + Guid estateId, + Guid merchantId, + CancellationToken cancellationToken); } public class EstateReportingApiClient : ClientProxyBase.ClientProxyBase, IEstateReportingApiClient { @@ -623,6 +636,61 @@ public async Task> GetTodaysSettlement(String accessTok } } + public async Task> GetMerchantSchedule(String accessToken, + Guid estateId, + Guid merchantId, + Int32 year, + CancellationToken cancellationToken) { + String requestUri = this.BuildRequestUrl($"/api/merchants/{merchantId}/schedule/{year}"); + + try + { + List<(String headerName, String headerValue)> additionalHeaders = [ + (EstateIdHeaderName, estateId.ToString()) + ]; + Result result = await this.SendHttpGetRequest(requestUri, accessToken, additionalHeaders, cancellationToken); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return result; + } + catch (Exception ex) + { + // An exception has occurred, add some additional information to the message + Exception exception = new Exception($"Error getting merchant schedule for merchant id {merchantId} for estate {estateId}.", ex); + + return Result.Failure(exception.Message); + } + } + + public async Task>> GetMerchantOpeningHours(String accessToken, + Guid estateId, + Guid merchantId, + CancellationToken cancellationToken) { + String requestUri = this.BuildRequestUrl($"/api/merchants/{merchantId}/openinghours"); + + try + { + List<(String headerName, String headerValue)> additionalHeaders = [ + (EstateIdHeaderName, estateId.ToString()) + ]; + Result> result = await this.SendHttpGetRequest>(requestUri, accessToken, additionalHeaders, cancellationToken); + + if (result.IsFailed) + return ResultHelpers.CreateFailure(result); + + return result; + } + catch (Exception ex) + { + // An exception has occurred, add some additional information to the message + Exception exception = new Exception($"Error getting merchant opening hours for merchant id {merchantId} for estate {estateId}.", ex); + + return Result.Failure(exception.Message); + } + } + public async Task>> GetMerchantDevices(String accessToken, Guid estateId, Guid merchantId, diff --git a/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs b/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs index 0b56f339..c5c2d0b4 100644 --- a/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs +++ b/EstateManagmentUI.BusinessLogic/Client/APIModelFactory.cs @@ -39,7 +39,7 @@ public static MerchantModels.MerchantKpiModel ConvertFrom(MerchantKpi apiResult) return model; } - public static MerchantModels.MerchantScheduleModel ConvertFrom(MerchantScheduleResponse apiResult) + public static MerchantModels.MerchantScheduleModel ConvertFrom(BackendAPI.DataTransferObjects.MerchantScheduleResponse apiResult) { MerchantModels.MerchantScheduleModel model = new() { @@ -246,6 +246,45 @@ public static TodaysSettlementModel ConvertFrom(TodaysSettlement apiResultData) }; return model; } + + public static MerchantModels.MerchantOpeningHoursModel ConvertFrom(List apiResultData) { + MerchantModels.MerchantOpeningHoursModel model = new(); + foreach (MerchantOpeningHour merchantOpeningHour in apiResultData) { + AssignOpeningHours(model, merchantOpeningHour.DayOfWeek, new MerchantModels.DayOpeningHoursModel() { Opening = merchantOpeningHour.OpeningTime, Closing = merchantOpeningHour.ClosingTime }); + } + + return model; + } + + private static void AssignOpeningHours(MerchantModels.MerchantOpeningHoursModel model, DayOfWeek dayOfWeek, MerchantModels.DayOpeningHoursModel dayModel) + { + switch (dayOfWeek) + { + case DayOfWeek.Sunday: + model.Sunday = dayModel; + break; + case DayOfWeek.Monday: + model.Monday = dayModel; + break; + case DayOfWeek.Tuesday: + model.Tuesday = dayModel; + break; + case DayOfWeek.Wednesday: + model.Wednesday = dayModel; + break; + case DayOfWeek.Thursday: + model.Thursday = dayModel; + break; + case DayOfWeek.Friday: + model.Friday = dayModel; + break; + case DayOfWeek.Saturday: + model.Saturday = dayModel; + break; + default: + throw new ArgumentOutOfRangeException(nameof(dayOfWeek), dayOfWeek, "Unsupported day of week."); + } + } } public static class FactoryExtensions{ @@ -385,107 +424,11 @@ public static MerchantModels.MerchantModel ToMerchant(this Merchant apiResultDat Region = apiResultData.Region, PostalCode = apiResultData.PostCode, SettlementSchedule = ((SettlementSchedule)apiResultData.SettlementSchedule).ToString(), - Town = apiResultData.Town, - OpeningHours = apiResultData.OpeningHours.ToMerchantOpeningHours() + Town = apiResultData.Town }; return model; } - - private static MerchantModels.MerchantOpeningHoursModel ToMerchantOpeningHours(this Dictionary openingHours) { - MerchantModels.MerchantOpeningHoursModel model = new(); - - if (openingHours == null) - { - MerchantModels.DayOpeningHoursModel sunday = new() - { - Opening = "00:00", - Closing = "00:59" - }; - MerchantModels.DayOpeningHoursModel monday = new() - { - Opening = "01:00", - Closing = "01:59" - }; - MerchantModels.DayOpeningHoursModel tuesday = new() - { - Opening = "02:00", - Closing = "02:59" - }; - MerchantModels.DayOpeningHoursModel wednesday = new() - { - Opening = "03:00", - Closing = "03:59" - }; - MerchantModels.DayOpeningHoursModel thursday = new() - { - Opening = "04:00", - Closing = "04:59" - }; - MerchantModels.DayOpeningHoursModel friday = new() - { - Opening = "05:00", - Closing = "05:59" - }; - MerchantModels.DayOpeningHoursModel saturday = new() - { - Opening = "06:00", - Closing = "06:59" - }; - - AssignOpeningHours(model, DayOfWeek.Sunday, sunday); - AssignOpeningHours(model, DayOfWeek.Monday, monday); - AssignOpeningHours(model, DayOfWeek.Tuesday, tuesday); - AssignOpeningHours(model, DayOfWeek.Wednesday, wednesday); - AssignOpeningHours(model, DayOfWeek.Thursday, thursday); - AssignOpeningHours(model, DayOfWeek.Friday, friday); - AssignOpeningHours(model, DayOfWeek.Saturday, saturday); - return model; - } - - foreach (KeyValuePair entry in openingHours) - { - MerchantModels.DayOpeningHoursModel dayModel = new() - { - Opening = entry.Value.Opening, - Closing = entry.Value.Closing - }; - - AssignOpeningHours(model, entry.Key, dayModel); - } - - return model; - } - - private static void AssignOpeningHours(MerchantModels.MerchantOpeningHoursModel model, DayOfWeek dayOfWeek, MerchantModels.DayOpeningHoursModel dayModel) - { - switch (dayOfWeek) - { - case DayOfWeek.Sunday: - model.Sunday = dayModel; - break; - case DayOfWeek.Monday: - model.Monday = dayModel; - break; - case DayOfWeek.Tuesday: - model.Tuesday = dayModel; - break; - case DayOfWeek.Wednesday: - model.Wednesday = dayModel; - break; - case DayOfWeek.Thursday: - model.Thursday = dayModel; - break; - case DayOfWeek.Friday: - model.Friday = dayModel; - break; - case DayOfWeek.Saturday: - model.Saturday = dayModel; - break; - default: - throw new ArgumentOutOfRangeException(nameof(dayOfWeek), dayOfWeek, "Unsupported day of week."); - } - } - + public static List ToMerchantOperators(this List apiResultData) { List merchantOperators = new(); diff --git a/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs b/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs index 9445226a..f981654f 100644 --- a/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs +++ b/EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs @@ -1,4 +1,5 @@ -using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; +using Azure.Core; +using EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects; using EstateManagementUI.BusinessLogic.Models; using EstateManagementUI.BusinessLogic.Requests; using SecurityService.DataTransferObjects.Responses; @@ -7,6 +8,7 @@ using TransactionProcessor.DataTransferObjects.Requests.Merchant; using TransactionProcessor.DataTransferObjects.Requests.MerchantSchedule; using TransactionProcessor.DataTransferObjects.Responses.Merchant; +using MerchantScheduleResponse = EstateManagementUI.BusinessLogic.BackendAPI.DataTransferObjects.MerchantScheduleResponse; namespace EstateManagementUI.BusinessLogic.Client { @@ -34,6 +36,9 @@ public partial interface IApiClient Task SwapMerchantDevice(MerchantCommands.SwapMerchantDeviceCommand request, CancellationToken cancellationToken); Task MakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand request, CancellationToken cancellationToken); Task CreateMerchant(MerchantCommands.CreateMerchantCommand request, CancellationToken cancellationToken); + Task> GetMerchantOpeningHours(MerchantQueries.GetMerchantOpeningHoursQuery request, + CancellationToken cancellationToken); + Task UpdateMerchantSchedule(MerchantCommands.UpdateMerchantScheduleCommand request, CancellationToken cancellationToken); } public partial class ApiClient : IApiClient { @@ -155,6 +160,46 @@ public async Task CreateMerchant(MerchantCommands.CreateMerchantCommand return Result.Success(); } + public async Task> GetMerchantOpeningHours(MerchantQueries.GetMerchantOpeningHoursQuery request, + CancellationToken cancellationToken) { + Result token = await this.GetToken(cancellationToken); + if (token.IsFailed) + return ResultHelpers.CreateFailure(token); + + Result> apiResult = await this.EstateReportingApiClient.GetMerchantOpeningHours(token.Data, request.EstateId, request.MerchantId, cancellationToken); + + if (apiResult.IsFailed) + return ResultHelpers.CreateFailure(apiResult); + + MerchantModels.MerchantOpeningHoursModel merchantOpeningHours= APIModelFactory.ConvertFrom(apiResult.Data); + + return Result.Success(merchantOpeningHours); + } + + public async Task UpdateMerchantSchedule(MerchantCommands.UpdateMerchantScheduleCommand request, + CancellationToken cancellationToken) { + var token = await this.GetToken(cancellationToken); + if (token.IsFailed) + return ResultHelpers.CreateFailure(token); + + UpdateMerchantScheduleRequest apiRequest = new() + { + Months = request.Schedule.Months + .OrderBy(month => month.Month) + .Select(month => new MerchantScheduleMonthRequest + { + Month = month.Month, + ClosedDays = month.ClosedDays.OrderBy(day => day).ToList() + }).ToList() + }; + + Result apiResult = await this.TransactionProcessorClient.UpdateMerchantSchedule(token.Data, request.EstateId, request.MerchantId, request.Schedule.Year, apiRequest, cancellationToken); + if (apiResult.IsFailed) + return ResultHelpers.CreateFailure(apiResult); + + return Result.Success(); + } + public async Task CreateMerchantSchedule(MerchantCommands.CreateMerchantScheduleCommand request, CancellationToken cancellationToken) { var token = await this.GetToken(cancellationToken); @@ -170,7 +215,7 @@ public async Task CreateMerchantSchedule(MerchantCommands.CreateMerchant ClosedDays = month.ClosedDays.OrderBy(day => day).ToList() }).ToList() }; - + Result apiResult = await this.TransactionProcessorClient.CreateMerchantSchedule(token.Data, request.EstateId, request.MerchantId, apiRequest, cancellationToken); if (apiResult.IsFailed) return ResultHelpers.CreateFailure(apiResult); @@ -266,7 +311,7 @@ public async Task RemoveOperatorFromMerchant(MerchantCommands.RemoveOper if (token.IsFailed) return ResultHelpers.CreateFailure(token); - Result apiResult = await this.TransactionProcessorClient.GetMerchantSchedule(token.Data, request.EstateId, request.MerchantId, request.Year, cancellationToken); + Result apiResult = await this.EstateReportingApiClient.GetMerchantSchedule(token.Data, request.EstateId, request.MerchantId, request.Year, cancellationToken); if (apiResult.IsFailed) return ResultHelpers.CreateFailure(apiResult); diff --git a/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj b/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj index 85ca2988..9e5e6521 100644 --- a/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj +++ b/EstateManagmentUI.BusinessLogic/EstateManagementUI.BusinessLogic.csproj @@ -12,6 +12,6 @@ - + diff --git a/EstateManagmentUI.BusinessLogic/Models/MerchantModels.cs b/EstateManagmentUI.BusinessLogic/Models/MerchantModels.cs index a2df661b..7aca6ba5 100644 --- a/EstateManagmentUI.BusinessLogic/Models/MerchantModels.cs +++ b/EstateManagmentUI.BusinessLogic/Models/MerchantModels.cs @@ -12,8 +12,6 @@ public class MerchantModel { public decimal? Balance { get; set; } public decimal? AvailableBalance { get; set; } public string? SettlementSchedule { get; set; } - public MerchantOpeningHoursModel OpeningHours { get; set; } = new(); - public Guid AddressId { get; set; } public string? AddressLine1 { get; set; } public string? AddressLine2 { get; set; } diff --git a/EstateManagmentUI.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs b/EstateManagmentUI.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs index fd716154..fa857bb6 100644 --- a/EstateManagmentUI.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs +++ b/EstateManagmentUI.BusinessLogic/RequestHandlers/MerchantRequestHandler.cs @@ -14,6 +14,7 @@ public class MerchantRequestHandler : IRequestHandler, IRequestHandler, IRequestHandler, + IRequestHandler, IRequestHandler, IRequestHandler, IRequestHandler, @@ -26,7 +27,8 @@ public class MerchantRequestHandler : IRequestHandler>>, IRequestHandler>>, IRequestHandler>>, - IRequestHandler> + IRequestHandler>, + IRequestHandler> { private readonly IApiClient ApiClient; @@ -147,4 +149,14 @@ public async Task Handle(MerchantCommands.CreateMerchantScheduleCommand CancellationToken cancellationToken) { return await this.ApiClient.GetMerchantKpi(request, cancellationToken); } + + public async Task> Handle(MerchantQueries.GetMerchantOpeningHoursQuery request, + CancellationToken cancellationToken) { + return await this.ApiClient.GetMerchantOpeningHours(request, cancellationToken); + } + + public async Task Handle(MerchantCommands.UpdateMerchantScheduleCommand request, + CancellationToken cancellationToken) { + return await this.ApiClient.UpdateMerchantSchedule(request, cancellationToken); + } } diff --git a/EstateManagmentUI.BusinessLogic/Requests/MerchantCommands.cs b/EstateManagmentUI.BusinessLogic/Requests/MerchantCommands.cs index 05df112b..47be37eb 100644 --- a/EstateManagmentUI.BusinessLogic/Requests/MerchantCommands.cs +++ b/EstateManagmentUI.BusinessLogic/Requests/MerchantCommands.cs @@ -19,15 +19,8 @@ public record SwapMerchantDeviceCommand(CorrelationId CorrelationId, Guid Estate public record MakeMerchantDepositCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, decimal Amount, DateTime Date, string Reference) : IRequest; public record CreateMerchantCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, string Name, String SettlementSchedule, MerchantAddress MerchantAddress, MerchantContact MerchantContact) : IRequest; public record CreateMerchantScheduleCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, MerchantModels.MerchantScheduleModel Schedule) : IRequest; - - + public record UpdateMerchantScheduleCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, MerchantModels.MerchantScheduleModel Schedule) : IRequest; public record MerchantOpeningHours(OpeningHours Sunday, OpeningHours Monday, OpeningHours Tuesday, OpeningHours Wednesday, OpeningHours Thursday, OpeningHours Friday, OpeningHours Saturday); - public record OpeningHours(string Opening, string Closing); - //public class OpeningHoursResponse - //{ - // public string Opening { get; set; } - // public string Closing { get; set; } - //} } diff --git a/EstateManagmentUI.BusinessLogic/Requests/MerchantQueries.cs b/EstateManagmentUI.BusinessLogic/Requests/MerchantQueries.cs index 6966e4f3..b971de82 100644 --- a/EstateManagmentUI.BusinessLogic/Requests/MerchantQueries.cs +++ b/EstateManagmentUI.BusinessLogic/Requests/MerchantQueries.cs @@ -14,4 +14,5 @@ public record GetMerchantScheduleQuery(CorrelationId CorrelationId, Guid EstateI public record GetMerchantOperatorsQuery(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId) : IRequest>>; public record GetMerchantContractsQuery(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId) : IRequest>>; public record GetMerchantDevicesQuery(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId) : IRequest>>; + public record GetMerchantOpeningHoursQuery(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId) : IRequest>; } From a496ccbf02e896cd3c8d067f7e4c0de15571ac1a Mon Sep 17 00:00:00 2001 From: StuartFerguson Date: Fri, 10 Apr 2026 16:51:26 +0100 Subject: [PATCH 2/2] fix failing unit tests --- .../Merchants/MerchantSchedulePageTests.cs | 27 +++++- .../UIServices/MerchantUIServiceTests.cs | 88 ++++++++++++++++--- 2 files changed, 101 insertions(+), 14 deletions(-) diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs index d21a27f7..9c009160 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantSchedulePageTests.cs @@ -232,6 +232,28 @@ public void MerchantSchedule_PreviousYear_IsReadOnly() [Fact] public void MerchantSchedule_ReadOnlyMode_DisablesEditingControls() { + //var merchantId = Guid.NewGuid(); + //var currentYear = DateTime.Today.Year; + + //SetupPageData(merchantId, currentYear, new MerchantModels.MerchantScheduleModel + //{ + // Year = currentYear, + // Months = new List + // { + // new() { Month = currentYear == DateTime.Today.Year && DateTime.Today.Month == 12 ? 12 : DateTime.Today.Month + 1, ClosedDays = new List { 1, 2 } } + // } + //}); + + //_fakeNavigationManager.NavigateTo($"/merchants/{merchantId}/schedule?readOnly=true"); + + //var cut = RenderComponent(parameters => parameters + // .Add(p => p.MerchantId, merchantId)); + //cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + //cut.Markup.ShouldContain("Selected Year Schedule"); + //cut.Find("#month-1-closed-days").HasAttribute("disabled").ShouldBeTrue(); + //cut.FindAll("#clonePreviousYearButton").Count.ShouldBe(0); + //cut.FindAll("#saveScheduleButton").Count.ShouldBe(0); var merchantId = Guid.NewGuid(); var currentYear = DateTime.Today.Year; @@ -244,10 +266,9 @@ public void MerchantSchedule_ReadOnlyMode_DisablesEditingControls() } }); - _fakeNavigationManager.NavigateTo($"/merchants/{merchantId}/schedule?readOnly=true"); - var cut = RenderComponent(parameters => parameters - .Add(p => p.MerchantId, merchantId)); + .Add(p => p.MerchantId, merchantId) + .Add(p => p.ReadOnly, true)); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); cut.Markup.ShouldContain("Selected Year Schedule"); diff --git a/EstateManagementUI.BlazorServer.Tests/UIServices/MerchantUIServiceTests.cs b/EstateManagementUI.BlazorServer.Tests/UIServices/MerchantUIServiceTests.cs index ca57a3dc..8a8637df 100644 --- a/EstateManagementUI.BlazorServer.Tests/UIServices/MerchantUIServiceTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/UIServices/MerchantUIServiceTests.cs @@ -597,7 +597,7 @@ public async Task GetMerchantSchedule_ReturnsFailure_WhenMediatorFails() } [Fact] - public async Task SaveMerchantSchedule_SendsCreateCommand_AndReturnsSuccess() + public async Task SaveMerchantSchedule_NewSchedule_SendsCreateCommand_AndReturnsSuccess() { var estateId = Guid.NewGuid(); var merchantId = Guid.NewGuid(); @@ -610,7 +610,9 @@ public async Task SaveMerchantSchedule_SendsCreateCommand_AndReturnsSuccess() new() { Month = 2, ClosedDays = new List() } } }; - + _mockMediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.NotFound()); _mockMediator .Setup(m => m.Send(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Success); @@ -618,19 +620,54 @@ public async Task SaveMerchantSchedule_SendsCreateCommand_AndReturnsSuccess() var result = await _service.SaveMerchantSchedule(CorrelationIdHelper.New(), estateId, merchantId, schedule); result.IsSuccess.ShouldBeTrue(); - _mockMediator.Verify(m => m.Send(It.Is(c => - c.EstateId == estateId && - c.MerchantId == merchantId && - c.Schedule.Year == schedule.Year && - c.Schedule.Months.Count == 2 && - c.Schedule.Months[0].Month == 1 && - c.Schedule.Months[0].ClosedDays.SequenceEqual(new[] { 1, 2, 15 }) - ), It.IsAny()), Times.Once); + _mockMediator.Verify(m => m.Send(It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public async Task SaveMerchantSchedule_ReturnsFailure_WhenMediatorFails() + public async Task SaveMerchantSchedule_ExistingSchedule_SendsUpdateCommand_AndReturnsSuccess() { + var estateId = Guid.NewGuid(); + var merchantId = Guid.NewGuid(); + var schedule = new BlazorServer.Models.MerchantModels.MerchantScheduleModel + { + Year = DateTime.Today.Year + 1, + Months = new List + { + new() { Month = 1, ClosedDays = new List { 1, 2, 15 } }, + new() { Month = 2, ClosedDays = new List() } + } + }; + + var year = DateTime.Today.Year; + var businessLogicSchedule = new BusinessLogic.Models.MerchantModels.MerchantScheduleModel + { + Year = year, + Months = new List + { + new() { Month = 1, ClosedDays = new List { 1, 2, 15 } } + } + }; + + _mockMediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(businessLogicSchedule)); + _mockMediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success); + + var result = await _service.SaveMerchantSchedule(CorrelationIdHelper.New(), estateId, merchantId, schedule); + + result.IsSuccess.ShouldBeTrue(); + _mockMediator.Verify(m => m.Send(It.IsAny(), It.IsAny()), Times.Once); + } + + [Fact] + public async Task SaveMerchantSchedule_NewSchedule_ReturnsFailure_WhenMediatorFails() + { + + _mockMediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.NotFound()); _mockMediator .Setup(m => m.Send(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Failure("err")); @@ -644,6 +681,35 @@ public async Task SaveMerchantSchedule_ReturnsFailure_WhenMediatorFails() result.IsFailed.ShouldBeTrue(); } + [Fact] + public async Task SaveMerchantSchedule_ExistingSchedule_ReturnsFailure_WhenMediatorFails() + { + var year = DateTime.Today.Year; + var businessLogicSchedule = new BusinessLogic.Models.MerchantModels.MerchantScheduleModel + { + Year = year, + Months = new List + { + new() { Month = 1, ClosedDays = new List { 1, 2, 15 } } + } + }; + + _mockMediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Success(businessLogicSchedule)); + _mockMediator + .Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(Result.Failure("err")); + + var result = await _service.SaveMerchantSchedule(CorrelationIdHelper.New(), Guid.NewGuid(), Guid.NewGuid(), new BlazorServer.Models.MerchantModels.MerchantScheduleModel + { + Year = DateTime.Today.Year, + Months = new List() + }); + + result.IsFailed.ShouldBeTrue(); + } + [Fact] public async Task AddOperatorToMerchant_ReturnsFailure_WhenMediatorFails() {