Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,6 @@

<PageTitle>Make Merchant Deposit</PageTitle>

@if (!hasPermission)
{
<div class="space-y-6">
<div class="bg-red-50 border border-red-200 text-red-700 px-6 py-8 rounded-lg text-center">
<svg class="w-16 h-16 mx-auto mb-4 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
</svg>
<h2 class="text-2xl font-bold mb-2">Access Denied</h2>
<p class="text-lg mb-4">You don't have permission to make deposits.</p>
<p class="mb-6">This operation requires the MakeDeposit permission for the Merchant section.</p>
<button class="btn btn-primary" @onclick="@(() => NavigationManager.NavigateTo("/merchants"))">
Back to Merchants
</button>
</div>
</div>
}
else
{
<div class="space-y-6">
<!-- Page Header -->
<div class="flex items-center justify-between">
Expand All @@ -51,6 +33,14 @@ else
</div>
}

@if (!string.IsNullOrWhiteSpace(successMessage))
{
<div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-lg">
<p class="font-medium">Success</p>
<p class="text-sm">@successMessage</p>
</div>
}

<!-- Form -->
<div class="bg-white rounded-lg shadow-md p-6">
<EditForm Model="@model" OnValidSubmit="@HandleSubmit">
Expand Down Expand Up @@ -103,119 +93,4 @@ else
</div>
</EditForm>
</div>
</div>
}

@code {
[Parameter]
public Guid MerchantId { get; set; }

private DepositModel model = new();
private bool isSaving = false;
private string? errorMessage;
private string? merchantName;
private bool hasPermission = false;

protected override async Task OnInitializedAsync()
{
await RequirePermission(PermissionSection.Merchant, PermissionFunction.MakeDeposit);

// Set default date to today
model.Date = DateTime.Today;

// Load merchant name
try
{
var correlationId = new CorrelationId(Guid.NewGuid());
var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111");
var accessToken = "stubbed-token";

var result = await Mediator.Send(new MerchantQueries.GetMerchantQuery(correlationId, estateId, MerchantId));
if (result.IsSuccess && result.Data != null)
{
merchantName = result.Data.MerchantName;
}
}
catch (Exception ex)
{
errorMessage = $"Failed to load merchant details: {ex.Message}";
}
}

private async Task HandleSubmit()
{
isSaving = true;
errorMessage = null;

try
{
var correlationId = new CorrelationId(Guid.NewGuid());
var estateId = Guid.Parse("11111111-1111-1111-1111-111111111111");
var accessToken = "stubbed-token";

var command = new Commands.MakeMerchantDepositCommand(
correlationId,
accessToken,
estateId,
MerchantId,
model.Amount,
model.Date,
model.Reference!
);

var result = await Mediator.Send(command);

if (!result.IsSuccess)
{
errorMessage = result.Message ?? "Failed to make deposit";
return;
}

// Navigate back to merchant list on success
NavigationManager.NavigateTo("/merchants");
}
catch (Exception ex)
{
errorMessage = $"An error occurred: {ex.Message}";
}
finally
{
isSaving = false;
}
}

private void Cancel()
{
NavigationManager.NavigateTo("/merchants");
}

public class DepositModel
{
[Required(ErrorMessage = "Deposit amount is required")]
[Range(1, int.MaxValue, ErrorMessage = "Deposit amount must be greater than 0")]
public int Amount { get; set; }

[Required(ErrorMessage = "Date of deposit is required")]
[DateNotInFuture(ErrorMessage = "Date cannot be in the future")]
public DateTime Date { get; set; } = DateTime.Today;

[Required(ErrorMessage = "Reference is required")]
public string? Reference { get; set; }
}

// Custom validation attribute for date not in future
private class DateNotInFutureAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value is DateTime date)
{
if (date.Date > DateTime.Today)
{
return new ValidationResult(ErrorMessage ?? "Date cannot be in the future");
}
}
return ValidationResult.Success;
}
}
}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using EstateManagementUI.BlazorServer.Permissions;
using EstateManagementUI.BusinessLogic.Requests;
using Microsoft.AspNetCore.Components;
using System.ComponentModel.DataAnnotations;

namespace EstateManagementUI.BlazorServer.Components.Pages.Merchants
{
public partial class Deposit
{
[Parameter]
public Guid MerchantId { get; set; }

private DepositModel model = new();
private bool isSaving = false;
private string? errorMessage;
private string? successMessage;
private string? merchantName;
private bool hasPermission = false;

protected override async Task OnAfterRenderAsync(bool firstRender) {
if (!firstRender) {
await base.OnAfterRenderAsync(firstRender);
return;
}

await RequirePermission(PermissionSection.Merchant, PermissionFunction.MakeDeposit);

// Set default date to today
model.Date = DateTime.Today;

// Load merchant name
try
{
var correlationId = new CorrelationId(Guid.NewGuid());
var estateId = await this.GetEstateId();

var result = await Mediator.Send(new MerchantQueries.GetMerchantQuery(correlationId, estateId, MerchantId));
if (result.IsSuccess && result.Data != null)
{
merchantName = result.Data.MerchantName;
}
}
catch (Exception ex)
{
errorMessage = $"Failed to load merchant details: {ex.Message}";
}
}

private async Task HandleSubmit()
{
isSaving = true;
errorMessage = null;
successMessage = null;

try
{
var correlationId = new CorrelationId(Guid.NewGuid());
var estateId = await this.GetEstateId();

var command = new MerchantCommands.MakeMerchantDepositCommand(
correlationId,
estateId,
MerchantId,
model.Amount,
model.Date,
model.Reference!
);

var result = await Mediator.Send(command);

if (!result.IsSuccess)
{
errorMessage = result.Message ?? "Failed to make deposit";
return;
}

// Show success message briefly before navigating away
successMessage = "Deposit recorded successfully.";
StateHasChanged();

// Small delay so user sees confirmation (adjust duration as needed)
await Task.Delay(2500);

// Navigate back to merchant list on success
NavigationManager.NavigateTo("/merchants");
}
catch (Exception ex)
{
errorMessage = $"An error occurred: {ex.Message}";
}
finally
{
isSaving = false;
}
}

private void Cancel()
{
NavigationManager.NavigateTo("/merchants");
}

public class DepositModel
{
[Required(ErrorMessage = "Deposit amount is required")]
[Range(1, int.MaxValue, ErrorMessage = "Deposit amount must be greater than 0")]
public int Amount { get; set; }

[Required(ErrorMessage = "Date of deposit is required")]
[DateNotInFuture(ErrorMessage = "Date cannot be in the future")]
public DateTime Date { get; set; } = DateTime.Today;

[Required(ErrorMessage = "Reference is required")]
public string? Reference { get; set; }
}

// Custom validation attribute for date not in future
private class DateNotInFutureAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)

Check warning on line 119 in EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor.cs

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

EstateManagementUI.BlazorServer/Components/Pages/Merchants/Deposit.razor.cs#L119

Remove this unused method parameter 'validationContext'.
{
if (value is DateTime date)
{
if (date.Date > DateTime.Today)
{
return new ValidationResult(ErrorMessage ?? "Date cannot be in the future");
}
}
return ValidationResult.Success;
}
}
}
}
16 changes: 16 additions & 0 deletions EstateManagmentUI.BusinessLogic/Client/MerchantMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public partial interface IApiClient
Task<Result> AddContractToMerchant(MerchantCommands.AssignContractToMerchantCommand request, CancellationToken cancellationToken);
Task<Result> AddDeviceToMerchant(MerchantCommands.AddMerchantDeviceCommand request, CancellationToken cancellationToken);
Task<Result> SwapMerchantDevice(MerchantCommands.SwapMerchantDeviceCommand request, CancellationToken cancellationToken);
Task<Result> MakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand request, CancellationToken cancellationToken);
}

public partial class ApiClient : IApiClient {
Expand Down Expand Up @@ -106,6 +107,21 @@ public async Task<Result> SwapMerchantDevice(MerchantCommands.SwapMerchantDevice
return Result.Success();
}

public async Task<Result> MakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand request,
CancellationToken cancellationToken) {
var token = await this.GetToken(cancellationToken);
if (token.IsFailed)
return ResultHelpers.CreateFailure(token);

MakeMerchantDepositRequest apiRequest = new() { Amount = request.Amount, DepositDateTime = request.Date, Reference = request.Reference};

var apiResult = await this.TransactionProcessorClient.MakeMerchantDeposit(token.Data, request.EstateId, request.MerchantId,apiRequest, cancellationToken);
if (apiResult.IsFailed)
return ResultHelpers.CreateFailure(apiResult);

return Result.Success();
}

public async Task<Result> RemoveOperatorFromMerchant(MerchantCommands.RemoveOperatorFromMerchantCommand request,
CancellationToken cancellationToken) {
var token = await this.GetToken(cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class MerchantRequestHandler : IRequestHandler<MerchantQueries.GetMerchan
IRequestHandler<MerchantCommands.AddMerchantDeviceCommand, Result>,
IRequestHandler<MerchantCommands.AddOperatorToMerchantCommand, Result>,
IRequestHandler<Commands.CreateMerchantCommand, Result>,
IRequestHandler<Commands.MakeMerchantDepositCommand, Result>,
IRequestHandler<MerchantCommands.MakeMerchantDepositCommand, Result>,
IRequestHandler<MerchantCommands.RemoveContractFromMerchantCommand, Result>,
IRequestHandler<MerchantCommands.RemoveOperatorFromMerchantCommand, Result>,
IRequestHandler<MerchantCommands.SwapMerchantDeviceCommand, Result>,
Expand Down Expand Up @@ -97,9 +97,9 @@ public async Task<Result> Handle(Commands.CreateMerchantCommand request,
return Result.Success();
}

public async Task<Result> Handle(Commands.MakeMerchantDepositCommand request,
public async Task<Result> Handle(MerchantCommands.MakeMerchantDepositCommand request,
CancellationToken cancellationToken) {
return Result.Success();
return await this.ApiClient.MakeMerchantDeposit(request, cancellationToken);
}

public async Task<Result> Handle(MerchantCommands.RemoveContractFromMerchantCommand request,
Expand Down
3 changes: 2 additions & 1 deletion EstateManagmentUI.BusinessLogic/Requests/Requests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public record RemoveContractFromMerchantCommand(CorrelationId CorrelationId, Gui
public record AssignContractToMerchantCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, Guid ContractId) : IRequest<Result>;
public record AddMerchantDeviceCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, string DeviceIdentifier) : IRequest<Result>;
public record SwapMerchantDeviceCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, string OldDevice, string NewDevice) : IRequest<Result>;
public record MakeMerchantDepositCommand(CorrelationId CorrelationId, Guid EstateId, Guid MerchantId, decimal Amount, DateTime Date, string Reference) : IRequest<Result>;
}

public static class Commands
Expand All @@ -93,7 +94,7 @@ public record CreateContractCommand(CorrelationId CorrelationId, string AccessTo
public record CreateMerchantCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, string Name, string ContactName, string ContactEmail) : IRequest<Result>;
public record CreateMerchantUserCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid MerchantId, string EmailAddress, string Password) : IRequest<Result>;
public record CreateOperatorCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, string Name, bool RequireCustomMerchantNumber, bool RequireCustomTerminalNumber) : IRequest<Result>;
public record MakeMerchantDepositCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid MerchantId, decimal Amount, DateTime Date, string Reference) : IRequest<Result>;



public record UpdateOperatorCommand(CorrelationId CorrelationId, string AccessToken, Guid EstateId, Guid OperatorId, string Name, bool RequireCustomMerchantNumber, bool RequireCustomTerminalNumber) : IRequest<Result>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, Cancellation
MerchantCommands.AddMerchantDeviceCommand => Task.FromResult((TResponse)(object)Result.Success()),
MerchantCommands.SwapMerchantDeviceCommand => Task.FromResult((TResponse)(object)Result.Success()),
Commands.CreateMerchantUserCommand => Task.FromResult((TResponse)(object)Result.Success()),
Commands.MakeMerchantDepositCommand cmd => Task.FromResult((TResponse)(object)this.ExecuteMakeMerchantDeposit(cmd)),
MerchantCommands.MakeMerchantDepositCommand cmd => Task.FromResult((TResponse)(object)this.ExecuteMakeMerchantDeposit(cmd)),

_ => throw new NotImplementedException($"Request type {request.GetType().Name} is not implemented in test mediator")
};
Expand Down Expand Up @@ -299,7 +299,7 @@ private Result ExecuteRemoveOperatorFromMerchant(MerchantCommands.RemoveOperator
return Result.Success();
}

private Result ExecuteMakeMerchantDeposit(Commands.MakeMerchantDepositCommand cmd)
private Result ExecuteMakeMerchantDeposit(MerchantCommands.MakeMerchantDepositCommand cmd)
{
var merchant = this._testDataStore.GetMerchant(cmd.EstateId, cmd.MerchantId);
if (merchant == null)
Expand Down
Loading