Added the recurring payment Details API

This commit is contained in:
ashutosh.nehete 2025-11-05 17:38:29 +05:30
parent 7de4f5e922
commit f1527a97f1
10 changed files with 231 additions and 14 deletions

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.ViewModels.Expenses
{
public class BasicExpenseVM
{
public Guid Id { get; set; }
public string? ExpenseUId { get; set; }
public double Amount { get; set; }
}
}

View File

@ -0,0 +1,9 @@
namespace Marco.Pms.Model.ViewModels.Expenses
{
public class BasicPaymentRequestVM
{
public Guid Id { get; set; }
public string? PaymentRequestUID { get; set; }
public double Amount { get; set; }
}
}

View File

@ -0,0 +1,13 @@
namespace Marco.Pms.Model.ViewModels.Expenses
{
public class BasicRecurringPaymentVM
{
public Guid Id { get; set; }
public string? RecurringPaymentUID { get; set; }
public double Amount { get; set; }
public bool IsVariable { get; set; }
}
}

View File

@ -18,7 +18,7 @@ namespace Marco.Pms.Model.ViewModels.Expenses
public double? TaxAmount { get; set; }
public DateTime DueDate { get; set; }
public BasicProjectVM? Project { get; set; }
public string? RecurringPaymentUID { get; set; }
public BasicRecurringPaymentVM? RecurringPayment { get; set; }
public ExpensesCategoryMasterVM? ExpenseCategory { get; set; }
public ExpensesStatusMasterVM? ExpenseStatus { get; set; }
public string? PaidTransactionId { get; set; }

View File

@ -10,7 +10,7 @@ namespace Marco.Pms.Model.ViewModels.Expenses
public Guid Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? RecurringPaymentUID { get; set; }
public BasicRecurringPaymentVM? RecurringPayment { get; set; }
public string? PaymentRequestUID { get; set; }
public string? Payee { get; set; }
public CurrencyMaster? Currency { get; set; }

View File

@ -0,0 +1,36 @@
using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.TenantModels;
using Marco.Pms.Model.ViewModels.Activities;
using Marco.Pms.Model.ViewModels.Master;
using Marco.Pms.Model.ViewModels.Projects;
namespace Marco.Pms.Model.ViewModels.Expenses
{
public class RecurringPaymentDetailsVM
{
public Guid Id { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? RecurringPaymentUID { get; set; }
public string? Payee { get; set; }
public List<BasicEmployeeVM>? NotifyTo { get; set; }
public CurrencyMaster? Currency { get; set; }
public double Amount { get; set; }
public DateTime StrikeDate { get; set; }
public DateTime? LatestPRGeneratedAt { get; set; }
public BasicProjectVM? Project { get; set; }
public int PaymentBufferDays { get; set; }
public int NumberOfIteration { get; set; }
public List<BasicPaymentRequestVM>? PaymentRequests { get; set; }
public ExpensesCategoryMasterVM? ExpenseCategory { get; set; }
public RecurringPaymentStatus? Status { get; set; }
public PLAN_FREQUENCY Frequency { get; set; }
public bool IsVariable { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public BasicEmployeeVM? CreatedBy { get; set; }
public DateTime? UpdatedAt { get; set; }
public BasicEmployeeVM? UpdatedBy { get; set; }
}
}

View File

@ -222,6 +222,14 @@ namespace Marco.Pms.Services.Controllers
return StatusCode(response.StatusCode, response);
}
[HttpGet("get/recurring-payment/details/{id?}")]
public async Task<IActionResult> GetRecurringPaymentDetailsAsync(Guid? id, [FromQuery] string? recurringPaymentUId)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.GetRecurringPaymentDetailsAsync(id, recurringPaymentUId, loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response);
}
[HttpPost("recurring-payment/create")]
public async Task<IActionResult> CreateRecurringPayment([FromBody] CreateRecurringTemplateDto model)
{

View File

@ -263,6 +263,10 @@ namespace Marco.Pms.Services.MappingProfiles
CreateMap<PaymentRequestDto, PaymentRequest>();
CreateMap<PaymentRequest, PaymentRequestVM>();
CreateMap<PaymentRequest, PaymentRequestDetailsVM>();
CreateMap<PaymentRequest, BasicPaymentRequestVM>()
.ForMember(
dest => dest.PaymentRequestUID,
opt => opt.MapFrom(src => $"{src.UIDPrefix}/{src.UIDPostfix:D5}"));
CreateMap<Document, PaymentRequestAttachmentVM>();
#endregion
@ -272,6 +276,17 @@ namespace Marco.Pms.Services.MappingProfiles
CreateMap<CreateRecurringTemplateDto, RecurringPayment>();
CreateMap<UpdateRecurringTemplateDto, RecurringPayment>();
CreateMap<RecurringPayment, RecurringPaymentVM>();
CreateMap<RecurringPayment, RecurringPaymentDetailsVM>()
.ForMember(
dest => dest.RecurringPaymentUID,
opt => opt.MapFrom(src => $"{src.UIDPrefix}/{src.UIDPostfix:D5}"))
.ForMember(
dest => dest.NotifyTo,
opt => opt.MapFrom(src => new List<BasicEmployeeVM>()));
CreateMap<RecurringPayment, BasicRecurringPaymentVM>()
.ForMember(
dest => dest.RecurringPaymentUID,
opt => opt.MapFrom(src => $"{src.UIDPrefix}/{src.UIDPostfix:D5}"));
#endregion

View File

@ -1314,8 +1314,8 @@ namespace Marco.Pms.Services.Service
{
var result = _mapper.Map<PaymentRequestVM>(pr);
result.PaymentRequestUID = $"{pr.UIDPrefix}/{pr.UIDPostfix:D5}";
if (pr.RecurringPayment != null)
result.RecurringPaymentUID = $"{pr.RecurringPayment.UIDPrefix}/{pr.RecurringPayment.UIDPostfix:D5}";
//if (pr.RecurringPayment != null)
// result.RecurringPaymentUID = $"{pr.RecurringPayment.UIDPrefix}/{pr.RecurringPayment.UIDPostfix:D5}";
return result;
}).ToList();
@ -1404,7 +1404,7 @@ namespace Marco.Pms.Services.Service
if (!hasViewSelfPermission && !hasViewAllPermission && !hasReviewPermission && !hasApprovePermission && !hasProcessPermission)
{
_logger.LogWarning("Access DENIED: Employee {EmployeeId} has no permission to view payment requests.", loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "You do not have permission to view any payment request.", 200);
return ApiResponse<object>.SuccessResponse(new { }, "You do not have permission to view any payment request.", 200);
}
// Query payment request with all necessary navigation properties and validation constraints
@ -1440,7 +1440,7 @@ namespace Marco.Pms.Services.Service
{
_logger.LogWarning("Access DENIED: Employee {EmployeeId} lacks permission to view PaymentRequest {PaymentRequestId} created by another employee.",
loggedInEmployee.Id, paymentRequest.Id);
return ApiResponse<object>.SuccessResponse(new List<ExpenseList>(), "You do not have permission to view this payment request.", 200);
return ApiResponse<object>.SuccessResponse(new { }, "You do not have permission to view this payment request.", 200);
}
// Concurrently fetch next possible statuses and related permissions
@ -1534,8 +1534,8 @@ namespace Marco.Pms.Services.Service
// Map main response model and populate additional fields
var response = _mapper.Map<PaymentRequestDetailsVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
if (paymentRequest.RecurringPayment != null)
response.RecurringPaymentUID = $"{paymentRequest.RecurringPayment.UIDPrefix}/{paymentRequest.RecurringPayment.UIDPostfix:D5}";
//if (paymentRequest.RecurringPayment != null)
// response.RecurringPaymentUID = $"{paymentRequest.RecurringPayment.UIDPrefix}/{paymentRequest.RecurringPayment.UIDPostfix:D5}";
response.Attachments = attachmentVMs;
response.NextStatus = nextStatuses;
response.UpdateLogs = updateLogs.Select(ul =>
@ -1970,6 +1970,7 @@ namespace Marco.Pms.Services.Service
.Include(pr => pr.ExpenseCategory)
.FirstOrDefaultAsync(pr =>
pr.Id == model.PaymentRequestId &&
!pr.IsAdvancePayment &&
pr.ProjectId.HasValue &&
pr.ExpenseCategoryId.HasValue &&
pr.PaidById.HasValue &&
@ -2514,6 +2515,137 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("End GetRecurringPaymentListAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
public async Task<ApiResponse<object>> GetRecurringPaymentDetailsAsync(Guid? id, string? recurringPaymentUId, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start GetRecurringPaymentDetailsAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} with Id: {Id}, UID: {UID}",
loggedInEmployee.Id, tenantId, id ?? Guid.Empty, recurringPaymentUId ?? "");
try
{
// Validate input: require at least one identifier
if (!id.HasValue && string.IsNullOrWhiteSpace(recurringPaymentUId))
{
_logger.LogWarning("Invalid parameters: Both Id and RecurringPaymentUID are null or empty.");
return ApiResponse<object>.ErrorResponse("At least one parameter (Id or RecurringPaymentUID) must be provided.", "Invalid argument.", 400);
}
// Concurrent permission checks for view-self, view-all, and manage recurring payments
var hasViewSelfPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ViewSelfRecurring, loggedInEmployee.Id);
});
var hasViewAllPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ViewAllRecurring, loggedInEmployee.Id);
});
var hasManagePermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ManageRecurring, loggedInEmployee.Id);
});
await Task.WhenAll(hasViewSelfPermissionTask, hasViewAllPermissionTask, hasManagePermissionTask);
bool hasViewSelfPermission = hasViewSelfPermissionTask.Result;
bool hasViewAllPermission = hasViewAllPermissionTask.Result;
bool hasManagePermission = hasManagePermissionTask.Result;
// Deny access if user lacks all relevant permissions
if (!hasViewSelfPermission && !hasViewAllPermission && !hasManagePermission)
{
_logger.LogWarning("Access DENIED: Employee {EmployeeId} has no permission to view recurring payments.", loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(new { }, "You do not have permission to view any recurring payment.", 200);
}
// Query recurring payment by Id or UID with navigation and tenant checks
var recurringPayment = await _context.RecurringPayments
.Include(rp => rp.Currency)
.Include(rp => rp.Project)
.Include(rp => rp.ExpenseCategory)
.Include(rp => rp.Status)
.Include(rp => rp.CreatedBy).ThenInclude(e => e!.JobRole)
.Include(rp => rp.UpdatedBy).ThenInclude(e => e!.JobRole)
.Where(rp =>
(rp.Id == id || (rp.UIDPrefix + "/" + rp.UIDPostfix.ToString().PadLeft(5, '0')) == recurringPaymentUId) &&
rp.TenantId == tenantId &&
rp.Currency != null &&
rp.ExpenseCategory != null &&
rp.Status != null &&
rp.CreatedBy != null &&
rp.CreatedBy.JobRole != null)
.FirstOrDefaultAsync();
if (recurringPayment == null)
{
_logger.LogWarning("Recurring Payment not found: Id={Id}, UID={UID}, TenantId={TenantId}", id ?? Guid.Empty, recurringPaymentUId ?? "N/A", tenantId);
return ApiResponse<object>.ErrorResponse("Recurring Payment not found.", "Recurring payment not found.", 404);
}
// If user has only view-self permission and the recurring payment belongs to another employee, deny access
bool selfCheck = hasViewSelfPermission && !hasViewAllPermission && !hasManagePermission &&
recurringPayment.CreatedById != loggedInEmployee.Id;
if (selfCheck)
{
_logger.LogWarning("Access DENIED: Employee {EmployeeId} lacks permission to view RecurringPayment {RecurringPaymentId} created by another employee.",
loggedInEmployee.Id, recurringPayment.Id);
return ApiResponse<object>.SuccessResponse(new { }, "You do not have permission to view this recurring payment.", 200);
}
// Concurrently fetch employees notified on this recurring payment and relevant active payment requests
var employeeTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
var emails = recurringPayment.NotifyTo.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return await context.Employees
.Include(e => e.JobRole)
.Where(e => emails.Contains(e.Email) && e.TenantId == tenantId && e.IsActive)
.ToListAsync();
});
var paymentRequestTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.PaymentRequests
.Where(pr => pr.RecurringPaymentId == recurringPayment.Id && pr.TenantId == tenantId && pr.IsActive)
.Select(pr => _mapper.Map<BasicPaymentRequestVM>(pr))
.ToListAsync();
});
await Task.WhenAll(employeeTask, paymentRequestTask);
var employees = employeeTask.Result;
var paymentRequests = paymentRequestTask.Result;
// Map main response DTO and enrich with notification employees and payment requests
var response = _mapper.Map<RecurringPaymentDetailsVM>(recurringPayment);
response.NotifyTo = _mapper.Map<List<BasicEmployeeVM>>(employees);
response.PaymentRequests = paymentRequests;
_logger.LogInfo("Recurring payment details fetched successfully for RecurringPaymentId: {RecurringPaymentId} by EmployeeId: {EmployeeId}",
recurringPayment.Id, loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(response, "Recurring payment details fetched successfully.", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in GetRecurringPaymentDetailsAsync for TenantId={TenantId}, EmployeeId={EmployeeId}: {Message}", tenantId, loggedInEmployee.Id, ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred while fetching the recurring payment details.", ex.Message, 500);
}
finally
{
_logger.LogInfo("End GetRecurringPaymentDetailsAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
public async Task<ApiResponse<object>> CreateRecurringPaymentAsync(CreateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
@ -3370,9 +3502,3 @@ namespace Marco.Pms.Services.Service
#endregion
}
}
// Ensure the endDate is included if it matches the frequency
//if (dates.Last() != endDate && (endDate - strikeDate).Ticks % frequency.Ticks == 0)
//{
// dates.Add(endDate);
//}

View File

@ -30,6 +30,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
#region =================================================================== Recurring Payment Functions ===================================================================
Task<ApiResponse<object>> GetRecurringPaymentListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetRecurringPaymentDetailsAsync(Guid? id, string? recurringPaymentUId, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> CreateRecurringPaymentAsync(CreateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> EditRecurringPaymentAsync(Guid id, UpdateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> PaymentRequestConversionAsync(List<Guid> RecurringTemplateIds, Employee loggedInEmployee, Guid tenantId);