feat: Implement user permission checks and manual payment functionalities

- Added CheckUserPermissionQuery and CheckUserPermissionQueryHandler for permission validation.
- Introduced GetUserRolesQuery and GetUserRolesQueryHandler to retrieve user roles.
- Created IPermissionService interface and its implementation in PermissionService.
- Defined permission and role constants in PermissionDefinitions.
- Developed SetDefaultVatPercentageCommand and its handler for VAT configuration.
- Implemented GetCurrentVatPercentageQuery and handler to fetch current VAT settings.
- Added manual payment commands: CreateManualPayment, ApproveManualPayment, and RejectManualPayment with respective handlers and validators.
- Created GetManualPaymentsQuery and handler for retrieving manual payment records.
- Integrated gRPC services for manual payments with appropriate permission checks.
- Established Protobuf definitions for manual payment operations and metadata.
This commit is contained in:
masoodafar-web
2025-12-05 17:27:38 +03:30
parent 67b43fea7a
commit 4aa9f28f6e
51 changed files with 1294 additions and 107 deletions

View File

@@ -0,0 +1,10 @@
using MediatR;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ApproveManualPayment;
public class ApproveManualPaymentCommand : IRequest<bool>
{
public long ManualPaymentId { get; set; }
public string? ApprovalNote { get; set; }
}

View File

@@ -0,0 +1,41 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using Google.Protobuf.WellKnownTypes;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ApproveManualPayment;
public class ApproveManualPaymentCommandHandler : IRequestHandler<ApproveManualPaymentCommand, bool>
{
private readonly IApplicationContractContext _context;
private readonly ILogger<ApproveManualPaymentCommandHandler> _logger;
public ApproveManualPaymentCommandHandler(
IApplicationContractContext context,
ILogger<ApproveManualPaymentCommandHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<bool> Handle(ApproveManualPaymentCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("Approving manual payment via BFF. Id: {Id}", request.ManualPaymentId);
var grpcRequest = new ApproveManualPaymentRequest
{
ManualPaymentId = request.ManualPaymentId
};
if (!string.IsNullOrWhiteSpace(request.ApprovalNote))
{
grpcRequest.ApprovalNote = new StringValue { Value = request.ApprovalNote };
}
await _context.ManualPayments.ApproveManualPaymentAsync(grpcRequest, cancellationToken: cancellationToken);
return true;
}
}

View File

@@ -0,0 +1,14 @@
using FluentValidation;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.ApproveManualPayment;
public class ApproveManualPaymentCommandValidator : AbstractValidator<ApproveManualPaymentCommand>
{
public ApproveManualPaymentCommandValidator()
{
RuleFor(x => x.ManualPaymentId)
.GreaterThan(0)
.WithMessage("شناسه پرداخت دستی نامعتبر است.");
}
}

View File

@@ -0,0 +1,18 @@
using MediatR;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
public class CreateManualPaymentCommand : IRequest<CreateManualPaymentResponseDto>
{
public long UserId { get; set; }
public long Amount { get; set; }
public int Type { get; set; }
public string Description { get; set; } = string.Empty;
public string? ReferenceNumber { get; set; }
}
public class CreateManualPaymentResponseDto
{
public long Id { get; set; }
}

View File

@@ -0,0 +1,51 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using Google.Protobuf.WellKnownTypes;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
public class CreateManualPaymentCommandHandler : IRequestHandler<CreateManualPaymentCommand, CreateManualPaymentResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ILogger<CreateManualPaymentCommandHandler> _logger;
public CreateManualPaymentCommandHandler(
IApplicationContractContext context,
ILogger<CreateManualPaymentCommandHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<CreateManualPaymentResponseDto> Handle(CreateManualPaymentCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation(
"Creating manual payment via BFF for UserId {UserId}, Amount {Amount}, Type {Type}",
request.UserId,
request.Amount,
request.Type);
var grpcRequest = new CreateManualPaymentRequest
{
UserId = request.UserId,
Amount = request.Amount,
Type = (ManualPaymentType)request.Type,
Description = request.Description
};
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
{
grpcRequest.ReferenceNumber = new StringValue { Value = request.ReferenceNumber };
}
var response = await _context.ManualPayments.CreateManualPaymentAsync(grpcRequest, cancellationToken: cancellationToken);
return new CreateManualPaymentResponseDto
{
Id = response.Id
};
}
}

View File

@@ -0,0 +1,26 @@
using FluentValidation;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.CreateManualPayment;
public class CreateManualPaymentCommandValidator : AbstractValidator<CreateManualPaymentCommand>
{
public CreateManualPaymentCommandValidator()
{
RuleFor(x => x.UserId)
.GreaterThan(0)
.WithMessage("شناسه کاربر باید بزرگتر از صفر باشد.");
RuleFor(x => x.Amount)
.GreaterThan(0)
.WithMessage("مبلغ باید بزرگتر از صفر باشد.");
RuleFor(x => x.Description)
.NotEmpty()
.WithMessage("توضیحات الزامی است.");
RuleFor(x => x.Type)
.GreaterThan(0)
.WithMessage("نوع پرداخت دستی نامعتبر است.");
}
}

View File

@@ -0,0 +1,10 @@
using MediatR;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.RejectManualPayment;
public class RejectManualPaymentCommand : IRequest<bool>
{
public long ManualPaymentId { get; set; }
public string RejectionReason { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,36 @@
using BackOffice.BFF.Application.Common.Interfaces;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.RejectManualPayment;
public class RejectManualPaymentCommandHandler : IRequestHandler<RejectManualPaymentCommand, bool>
{
private readonly IApplicationContractContext _context;
private readonly ILogger<RejectManualPaymentCommandHandler> _logger;
public RejectManualPaymentCommandHandler(
IApplicationContractContext context,
ILogger<RejectManualPaymentCommandHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<bool> Handle(RejectManualPaymentCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("Rejecting manual payment via BFF. Id: {Id}", request.ManualPaymentId);
var grpcRequest = new RejectManualPaymentRequest
{
ManualPaymentId = request.ManualPaymentId,
RejectionReason = request.RejectionReason
};
await _context.ManualPayments.RejectManualPaymentAsync(grpcRequest, cancellationToken: cancellationToken);
return true;
}
}

View File

@@ -0,0 +1,18 @@
using FluentValidation;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Commands.RejectManualPayment;
public class RejectManualPaymentCommandValidator : AbstractValidator<RejectManualPaymentCommand>
{
public RejectManualPaymentCommandValidator()
{
RuleFor(x => x.ManualPaymentId)
.GreaterThan(0)
.WithMessage("شناسه پرداخت دستی نامعتبر است.");
RuleFor(x => x.RejectionReason)
.NotEmpty()
.WithMessage("دلیل رد الزامی است.");
}
}

View File

@@ -0,0 +1,15 @@
using MediatR;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Queries.GetManualPayments;
public class GetManualPaymentsQuery : IRequest<GetManualPaymentsResponseDto>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
public long? UserId { get; set; }
public int? Status { get; set; }
public int? Type { get; set; }
public long? RequestedBy { get; set; }
public bool OrderByDescending { get; set; } = true;
public string? ReferenceNumber { get; set; }
}

View File

@@ -0,0 +1,114 @@
using BackOffice.BFF.Application.Common.Interfaces;
using BackOffice.BFF.Application.Common.Models;
using CMSMicroservice.Protobuf.Protos.ManualPayment;
using Google.Protobuf.WellKnownTypes;
using MediatR;
using Microsoft.Extensions.Logging;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Queries.GetManualPayments;
public class GetManualPaymentsQueryHandler : IRequestHandler<GetManualPaymentsQuery, GetManualPaymentsResponseDto>
{
private readonly IApplicationContractContext _context;
private readonly ILogger<GetManualPaymentsQueryHandler> _logger;
public GetManualPaymentsQueryHandler(
IApplicationContractContext context,
ILogger<GetManualPaymentsQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<GetManualPaymentsResponseDto> Handle(GetManualPaymentsQuery request, CancellationToken cancellationToken)
{
_logger.LogInformation(
"Getting manual payments via BFF. Page: {Page}, PageSize: {PageSize}, Status: {Status}, Type: {Type}",
request.PageNumber,
request.PageSize,
request.Status,
request.Type);
var grpcRequest = new GetAllManualPaymentsRequest
{
PageNumber = request.PageNumber,
PageSize = request.PageSize
};
if (request.UserId.HasValue)
{
grpcRequest.UserId = new Int64Value { Value = request.UserId.Value };
}
if (request.Status.HasValue)
{
grpcRequest.Status = new Int32Value { Value = request.Status.Value };
}
if (request.Type.HasValue)
{
grpcRequest.Type = new Int32Value { Value = request.Type.Value };
}
if (request.RequestedBy.HasValue)
{
grpcRequest.RequestedBy = new Int64Value { Value = request.RequestedBy.Value };
}
grpcRequest.OrderByDescending = new BoolValue { Value = request.OrderByDescending };
var response = await _context.ManualPayments.GetAllManualPaymentsAsync(grpcRequest, cancellationToken: cancellationToken);
var meta = response.MetaData;
var models = response.Models?
.Select(m => new ManualPaymentModel
{
Id = m.Id,
UserId = m.UserId,
UserFullName = m.UserFullName,
UserMobile = m.UserMobile,
Amount = m.Amount,
Type = (int)m.Type,
TypeDisplay = m.TypeDisplay,
Description = m.Description,
ReferenceNumber = m.ReferenceNumber,
Status = (int)m.Status,
StatusDisplay = m.StatusDisplay,
RequestedBy = m.RequestedBy,
RequestedByName = m.RequestedByName,
ApprovedBy = m.ApprovedBy?.Value,
ApprovedByName = m.ApprovedByName,
ApprovedAt = m.ApprovedAt?.ToDateTime(),
RejectionReason = m.RejectionReason,
TransactionId = m.TransactionId?.Value,
Created = m.Created.ToDateTime()
})
.ToList()
?? new List<ManualPaymentModel>();
if (!string.IsNullOrWhiteSpace(request.ReferenceNumber))
{
models = models
.Where(m => !string.IsNullOrWhiteSpace(m.ReferenceNumber) &&
m.ReferenceNumber.Contains(request.ReferenceNumber!, StringComparison.OrdinalIgnoreCase))
.ToList();
}
var result = new GetManualPaymentsResponseDto
{
MetaData = new MetaData
{
CurrentPage = meta.CurrentPage,
TotalPage = meta.TotalPage,
PageSize = meta.PageSize,
TotalCount = meta.TotalCount,
HasPrevious = meta.HasPrevious,
HasNext = meta.HasNext
},
Models = models
};
return result;
}
}

View File

@@ -0,0 +1,33 @@
using BackOffice.BFF.Application.Common.Models;
namespace BackOffice.BFF.Application.ManualPaymentCQ.Queries.GetManualPayments;
public class GetManualPaymentsResponseDto
{
public MetaData MetaData { get; set; } = new();
public List<ManualPaymentModel>? Models { get; set; }
}
public class ManualPaymentModel
{
public long Id { get; set; }
public long UserId { get; set; }
public string UserFullName { get; set; } = string.Empty;
public string UserMobile { get; set; } = string.Empty;
public long Amount { get; set; }
public int Type { get; set; }
public string TypeDisplay { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string? ReferenceNumber { get; set; }
public int Status { get; set; }
public string StatusDisplay { get; set; } = string.Empty;
public long RequestedBy { get; set; }
public string RequestedByName { get; set; } = string.Empty;
public long? ApprovedBy { get; set; }
public string? ApprovedByName { get; set; }
public DateTime? ApprovedAt { get; set; }
public string? RejectionReason { get; set; }
public long? TransactionId { get; set; }
public DateTime Created { get; set; }
}