diff --git a/.github/workflows/prlinked.yml b/.github/workflows/prlinked.yml deleted file mode 100644 index 84037c86..00000000 --- a/.github/workflows/prlinked.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Move Linked Issues - -on: - pull_request: - types: - - opened - - synchronize - - reopened - -jobs: - get-date: - runs-on: ubuntu-latest - outputs: - project_name_prefix: ${{ steps.format_date.outputs.formatted_date }} - steps: - - name: Get PR creation date - id: format_date - run: | - # Extract the month and year from the PR creation date - PR_DATE="${{ github.event.pull_request.created_at }}" - FORMATTED_DATE=$(date -d "$PR_DATE" "+%B %Y") # Format to Month Year - - # Debugging: print out the formatted date - echo "Formatted Date: ${FORMATTED_DATE} Sprint" - - # Set output using the Environment File method - echo "formatted_date=${FORMATTED_DATE} Sprint" >> $GITHUB_OUTPUT # Set the output for later jobs - - debug-date: - needs: get-date - runs-on: ubuntu-latest - steps: - - name: Debug the outputs - run: | - echo "PR Number: ${{ github.event.pull_request.number }}" - echo "Project Column Name: Review" - echo "Project Name Prefix (from get-date job output): ${{ needs.get-date.outputs.project_name_prefix }}" # Access the output correctly - - move-issues: - needs: get-date - uses: TransactionProcessing/org-ci-workflows/.github/workflows/move-linked-issue.yml@main - with: - pr_number: ${{ github.event.pull_request.number }} - project_column_name: "Review" - project_name_prefix: ${{ needs.get-date.outputs.project_name_prefix }} # Access the output from get-date job - secrets: - gh_token: ${{ secrets.GH_TOKEN }} diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsDepositPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsDepositPageTests.cs index 5df319bc..fa4b0f39 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsDepositPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsDepositPageTests.cs @@ -170,6 +170,23 @@ public void Deposit_MakeDepositButton_ShowsErrorOnFailure() timeout: TimeSpan.FromSeconds(5)); } + [Fact] + public void Deposit_HasTwoCancelButtons() + { + // Arrange + SetupSuccessfulMerchantLoad(); + + // Act + IRenderedComponent cut = RenderComponent(parameters => + parameters.Add(p => p.MerchantId, _testMerchantId)); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Assert - there should be exactly 2 Cancel buttons + IRefreshableElementCollection allButtons = cut.FindAll("button"); + List cancelButtonList = allButtons.Where(b => b.TextContent.Trim() == "Cancel").ToList(); + cancelButtonList.Count.ShouldBe(2); + } + [Fact] public void Deposit_CancelButton_NavigatesToMerchantsIndex() { diff --git a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs index 895ac88b..2bc69cc4 100644 --- a/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs +++ b/EstateManagementUI.BlazorServer.Tests/Pages/Merchants/MerchantsIndexPageTests.cs @@ -413,7 +413,7 @@ public void MerchantsIndex_EditButton_NavigatesToEditMerchantPage() } [Fact] - public void MerchantsIndex_MakeDepositButton_NavigatesToDepositPage() + public void MerchantsIndex_MakeDepositButton_OpensDepositModal() { // Arrange var merchantId = Guid.NewGuid(); @@ -434,6 +434,13 @@ public void MerchantsIndex_MakeDepositButton_NavigatesToDepositPage() It.IsAny())) .ReturnsAsync(Result.Success(merchants)); + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(new MerchantModels.MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant" + })); + var cut = RenderComponent(); cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); @@ -441,8 +448,188 @@ public void MerchantsIndex_MakeDepositButton_NavigatesToDepositPage() var makeDepositButton = cut.Find("#makeDepositLink"); makeDepositButton.Click(); - // Assert - _fakeNavigationManager.Uri.ShouldContain($"/merchants/{merchantId}/deposit"); + // Assert - modal is shown with the deposit form + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Make Merchant Deposit"), TimeSpan.FromSeconds(5)); + cut.Markup.ShouldContain("depositAmount"); + cut.Markup.ShouldContain("depositDate"); + cut.Markup.ShouldContain("depositReference"); + } + + [Fact] + public void MerchantsIndex_DepositModal_HasOneCancelButton() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchants = new List + { + new() + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "REF001" + } + }; + + this.MerchantUIService.Setup(m => m.GetMerchants(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(Result.Success(merchants)); + + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(new MerchantModels.MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant" + })); + + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Open modal + cut.Find("#makeDepositLink").Click(); + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Make Merchant Deposit"), TimeSpan.FromSeconds(5)); + + // Assert - exactly 1 Cancel button in the modal footer + var allButtons = cut.FindAll("button"); + var cancelButtons = allButtons.Where(b => b.TextContent.Trim() == "Cancel").ToList(); + cancelButtons.Count.ShouldBe(1); + } + + [Fact] + public void MerchantsIndex_DepositModal_CancelButton_ClosesModal() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchants = new List + { + new() + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "REF001" + } + }; + + this.MerchantUIService.Setup(m => m.GetMerchants(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(Result.Success(merchants)); + + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(new MerchantModels.MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant" + })); + + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + cut.Find("#makeDepositLink").Click(); + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Make Merchant Deposit"), TimeSpan.FromSeconds(5)); + + // Act - click Cancel + var cancelButton = cut.FindAll("button").FirstOrDefault(b => b.TextContent.Trim() == "Cancel"); + cancelButton?.Click(); + + // Assert - modal is closed + cut.WaitForAssertion(() => cut.Markup.ShouldNotContain("depositAmount"), TimeSpan.FromSeconds(5)); + _fakeNavigationManager.Uri.ShouldNotContain("/deposit"); + } + + [Fact] + public void MerchantsIndex_DepositModal_Submit_ShowsSuccess() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchants = new List + { + new() + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "REF001" + } + }; + + this.MerchantUIService.Setup(m => m.GetMerchants(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(Result.Success(merchants)); + + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Success(new MerchantModels.MerchantModel + { + MerchantId = merchantId, + MerchantName = "Test Merchant" + })); + + this.MerchantUIService.Setup(m => m.MakeMerchantDeposit( + It.IsAny(), + It.IsAny(), + merchantId, + It.IsAny())) + .ReturnsAsync(Result.Success); + + var cut = RenderComponent(); + cut.Instance.SetDelayOverride(500); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + cut.Find("#makeDepositLink").Click(); + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Make Merchant Deposit"), TimeSpan.FromSeconds(5)); + + // Act - fill and submit form + cut.Find("#depositAmount").Change(100); + cut.Find("#depositDate").Change(DateTime.Today.ToString("yyyy-MM-dd")); + cut.Find("#depositReference").Change("TEST-REF-001"); + cut.Find("#makeDepositButton").Click(); + + // Assert - success message is shown before modal closes + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Deposit recorded successfully"), TimeSpan.FromSeconds(5)); + cut.WaitForAssertion(() => cut.Markup.ShouldNotContain("depositAmount"), TimeSpan.FromSeconds(5)); + } + + [Fact] + public void MerchantsIndex_DepositModal_OpensWithoutMerchantName_WhenGetMerchantFails() + { + // Arrange + var merchantId = Guid.NewGuid(); + var merchants = new List + { + new() + { + MerchantId = merchantId, + MerchantName = "Test Merchant", + MerchantReference = "REF001" + } + }; + + this.MerchantUIService.Setup(m => m.GetMerchants(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync(Result.Success(merchants)); + + this.MerchantUIService.Setup(m => m.GetMerchant(It.IsAny(), It.IsAny(), merchantId)) + .ReturnsAsync(Result.Failure()); + + var cut = RenderComponent(); + cut.WaitForState(() => !cut.Markup.Contains("animate-spin"), TimeSpan.FromSeconds(5)); + + // Act - click Make Deposit when GetMerchant fails + cut.Find("#makeDepositLink").Click(); + + // Assert - modal opens but without merchant name + cut.WaitForAssertion(() => cut.Markup.ShouldContain("Make Merchant Deposit"), TimeSpan.FromSeconds(5)); + cut.Markup.ShouldContain("depositAmount"); + cut.Markup.ShouldNotContain("For merchant:"); } [Fact] diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor index 24fc0f0a..b13f5026 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor @@ -51,7 +51,7 @@ - +

Please enter whole pounds only (no pence)

@@ -60,7 +60,7 @@ - +

Can be in the past for deposit catch-ups, but cannot be in the future

@@ -69,7 +69,7 @@ - + diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor index f2a8a874..434ceaf1 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor @@ -233,3 +233,84 @@ } + +@if (showDepositModal) +{ +
+
+
+

Make Merchant Deposit

+ @if (!string.IsNullOrEmpty(depositModalMerchantName)) + { +

For merchant: @depositModalMerchantName

+ } +
+ + @if (!string.IsNullOrWhiteSpace(depositErrorMessage)) + { +
+

Error

+

@depositErrorMessage

+
+ } + + @if (!string.IsNullOrWhiteSpace(depositSuccessMessage)) + { +
+

Success

+

@depositSuccessMessage

+
+ } + + + + +
+
+ + + +

Please enter whole pounds only (no pence)

+
+ +
+ + + +

Can be in the past for deposit catch-ups, but cannot be in the future

+
+ +
+ + + +
+
+ +
+ + +
+
+
+
+} + diff --git a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor.cs b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor.cs index 5f6c849d..6bee878a 100644 --- a/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor.cs +++ b/EstateManagementUI.BlazorServer/Components/Pages/Merchants/Index.razor.cs @@ -7,6 +7,7 @@ using Shared.Results; using SimpleResults; using MerchantListModel = EstateManagementUI.BlazorServer.Models.MerchantModels.MerchantListModel; +using MerchantModels = EstateManagementUI.BlazorServer.Models.MerchantModels; namespace EstateManagementUI.BlazorServer.Components.Pages.Merchants { @@ -76,6 +77,15 @@ private int pageSize private int _totalPages = 1; private int totalPages => _totalPages; + // Deposit modal fields + private bool showDepositModal = false; + private Guid depositMerchantId; + private string? depositModalMerchantName; + private MerchantModels.DepositModel depositModel = new(); + private bool isDepositSaving = false; + private string? depositErrorMessage; + private string? depositSuccessMessage; + protected override async Task OnAfterRenderAsync(bool firstRender) { if (!firstRender) @@ -163,7 +173,54 @@ private void LastPage() private void EditMerchant(Guid merchantId) => NavigationManager.NavigateToEditMerchant(merchantId); - private void MakeDeposit(Guid merchantId) => this.NavigationManager.NavigateToMakeMerchantDeposit(merchantId); + private async Task MakeDeposit(Guid merchantId) + { + depositMerchantId = merchantId; + depositModel = new MerchantModels.DepositModel(); + depositErrorMessage = null; + depositSuccessMessage = null; + + CorrelationId correlationId = new(Guid.NewGuid()); + Guid estateId = await this.GetEstateId(); + Result getMerchantResult = await this.MerchantUiService.GetMerchant(correlationId, estateId, merchantId); + depositModalMerchantName = getMerchantResult.IsSuccess ? getMerchantResult.Data.MerchantName : null; + + showDepositModal = true; + } + + private void CloseDepositModal() + { + showDepositModal = false; + depositModel = new MerchantModels.DepositModel(); + depositErrorMessage = null; + depositSuccessMessage = null; + } + + private async Task HandleDepositSubmit() + { + isDepositSaving = true; + depositErrorMessage = null; + depositSuccessMessage = null; + + CorrelationId correlationId = new(Guid.NewGuid()); + Guid estateId = await this.GetEstateId(); + + Result result = await this.MerchantUiService.MakeMerchantDeposit(correlationId, estateId, depositMerchantId, depositModel); + + if (result.IsSuccess) + { + depositSuccessMessage = "Deposit recorded successfully"; + StateHasChanged(); + await this.WaitOnUIRefresh(); + CloseDepositModal(); + } + else + { + depositErrorMessage = "Failed to make deposit"; + } + + isDepositSaving = false; + } private void NavigateToNewMerchant() => NavigationManager.NavigateToNewMerchant(); }