Added the API to get details of payment request

This commit is contained in:
ashutosh.nehete 2025-11-07 17:25:16 +05:30
parent 90b7cabaf5
commit c5adc6322d
3 changed files with 253 additions and 0 deletions

View File

@ -133,6 +133,14 @@ namespace Marco.Pms.Services.Controllers
var response = await _expensesService.GetPaymentRequestListAsync(searchString, filter, isActive, pageSize, pageNumber, loggedInEmployee, tenantId); var response = await _expensesService.GetPaymentRequestListAsync(searchString, filter, isActive, pageSize, pageNumber, loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response); return StatusCode(response.StatusCode, response);
} }
[HttpGet("get/payment-request/details/{id?}")]
public async Task<IActionResult> GetPaymentRequestDetails(Guid? id, [FromQuery] string? paymentRequestUId)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.GetPaymentRequestDetailsAsync(id, paymentRequestUId, loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response);
}
#endregion #endregion
#region =================================================================== Payment Request Functions =================================================================== #region =================================================================== Payment Request Functions ===================================================================

View File

@ -1364,7 +1364,251 @@ namespace Marco.Pms.Services.Service
_logger.LogInfo("End GetPaymentRequestListAsync for TenantId={TenantId}, EmployeeId={EmployeeId}", tenantId, loggedInEmployee.Id); _logger.LogInfo("End GetPaymentRequestListAsync for TenantId={TenantId}, EmployeeId={EmployeeId}", tenantId, loggedInEmployee.Id);
} }
} }
public async Task<ApiResponse<object>> GetPaymentRequestDetailsAsync(Guid? id, string? paymentRequestUId, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start GetPaymentRequestDetailsAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} with Id: {Id}, UID: {UID}",
loggedInEmployee.Id, tenantId, id ?? Guid.Empty, paymentRequestUId ?? "PY/1125/00000");
try
{
// Validate input: at least one identifier must be provided
if (!id.HasValue && string.IsNullOrWhiteSpace(paymentRequestUId))
{
_logger.LogWarning("Invalid parameters: Both Id and PaymentRequestUID are null or empty.");
return ApiResponse<object>.ErrorResponse("At least one parameter (Id or PaymentRequestUID) must be provided.", "Invalid argument.", 400);
}
// Check user permissions concurrently
var hasViewSelfPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ExpenseViewSelf, loggedInEmployee.Id);
});
var hasViewAllPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ExpenseViewAll, loggedInEmployee.Id);
});
var hasReviewPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ExpenseReview, loggedInEmployee.Id);
});
var hasApprovePermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ExpenseApprove, loggedInEmployee.Id);
});
var hasProcessPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ExpenseProcess, loggedInEmployee.Id);
});
var hasManagePermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ExpenseManage, loggedInEmployee.Id);
});
await Task.WhenAll(hasViewSelfPermissionTask, hasViewAllPermissionTask, hasReviewPermissionTask, hasApprovePermissionTask, hasProcessPermissionTask, hasManagePermissionTask);
bool hasViewSelfPermission = hasViewSelfPermissionTask.Result;
bool hasViewAllPermission = hasViewAllPermissionTask.Result;
bool hasReviewPermission = hasReviewPermissionTask.Result;
bool hasApprovePermission = hasApprovePermissionTask.Result;
bool hasProcessPermission = hasProcessPermissionTask.Result;
bool hasManagePermission = hasProcessPermissionTask.Result;
// Deny access if user has no relevant permissions
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 { }, "You do not have permission to view any payment request.", 200);
}
// Query payment request with all necessary navigation properties and validation constraints
var paymentRequest = await _context.PaymentRequests
.Include(pr => pr.Currency)
.Include(pr => pr.Project)
.Include(pr => pr.RecurringPayment)
.Include(pr => pr.ExpenseCategory)
.Include(pr => pr.ExpenseStatus)
.Include(pr => pr.CreatedBy).ThenInclude(e => e!.JobRole)
.Include(pr => pr.UpdatedBy).ThenInclude(e => e!.JobRole)
.Where(pr =>
(pr.Id == id || (pr.UIDPrefix + "/" + pr.UIDPostfix.ToString().PadLeft(5, '0')) == paymentRequestUId) &&
pr.TenantId == tenantId &&
pr.Currency != null &&
pr.ExpenseCategory != null &&
pr.ExpenseStatus != null &&
pr.CreatedBy != null &&
pr.CreatedBy.JobRole != null)
.FirstOrDefaultAsync();
if (paymentRequest == null)
{
_logger.LogWarning("Payment Request not found: Id={Id}, UID={UID}, TenantId={TenantId}", id ?? Guid.Empty, paymentRequestUId ?? "PY/1125/00000", tenantId);
return ApiResponse<object>.ErrorResponse("Payment Request not found.", "Payment Request not found.", 404);
}
// Check if employee has only "view self" permission but the payment request is created by another employee => deny
bool selfCheck = hasViewSelfPermission && !hasViewAllPermission && !hasReviewPermission && !hasApprovePermission && !hasProcessPermission
&& paymentRequest.CreatedById != loggedInEmployee.Id;
if (selfCheck)
{
_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 { }, "You do not have permission to view this payment request.", 200);
}
// Concurrently fetch next possible statuses and related permissions
var nextStatusTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
var nextStatuses = await context.ExpensesStatusMapping
.Include(esm => esm.NextStatus)
.Where(esm => esm.StatusId == paymentRequest.ExpenseStatusId && esm.NextStatus != null)
.Select(esm => esm.NextStatus!)
.ToListAsync();
var nextStatusIds = nextStatuses.Select(ns => ns.Id).ToList();
var permissionMappings = await context.StatusPermissionMapping.Where(spm => nextStatusIds.Contains(spm.StatusId)).ToListAsync();
var results = new List<ExpensesStatusMasterVM>();
foreach (var status in nextStatuses)
{
var permissionIds = permissionMappings.Where(spm => spm.StatusId == status.Id).Select(spm => spm.PermissionId).ToList();
bool hasPermission = await permissionService.HasPermissionAny(permissionIds, loggedInEmployee.Id);
// Special case: allow review status if creator is the logged-in user
bool hasStatusPermission = Review == status.Id && loggedInEmployee.Id == paymentRequest.CreatedById;
if (!hasPermission && !hasStatusPermission)
{
continue;
}
var mappedStatus = _mapper.Map<ExpensesStatusMasterVM>(status);
mappedStatus.PermissionIds = permissionIds;
results.Add(mappedStatus);
}
int index = results.FindIndex(ns => ns.DisplayName == "Reject");
if (index > -1)
{
var item = results[index];
results.RemoveAt(index);
results.Insert(0, item);
}
return results;
});
// Concurrently fetch attachments with pre-signed URLs
var documentTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
var documents = await context.PaymentRequestAttachments
.Include(pra => pra.Document)
.Where(pra => pra.PaymentRequestId == paymentRequest.Id && pra.Document != null)
.Select(pra => pra.Document!)
.ToListAsync();
return documents.Select(d =>
{
var attachmentVM = _mapper.Map<PaymentRequestAttachmentVM>(d);
attachmentVM.Url = _s3Service.GeneratePreSignedUrl(d.S3Key);
return attachmentVM;
}).ToList();
});
var updateLogsTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.StatusUpdateLogs
.Include(sul => sul.UpdatedBy)
.Where(sul => sul.EntityId == paymentRequest.Id && sul.TenantId == tenantId)
.OrderByDescending(sul => sul.UpdatedAt)
.ToListAsync();
});
await Task.WhenAll(nextStatusTask, documentTask, updateLogsTask);
var nextStatuses = nextStatusTask.Result;
var attachmentVMs = documentTask.Result;
var updateLogs = updateLogsTask.Result;
var statusIds = updateLogs.Select(sul => sul.StatusId).ToList();
statusIds.AddRange(updateLogs.Select(sul => sul.NextStatusId).ToList());
statusIds = statusIds.Distinct().ToList();
var status = await _context.ExpensesStatusMaster.Where(es => statusIds.Contains(es.Id)).ToListAsync();
// 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}";
response.Attachments = attachmentVMs;
// Assign nextStatuses only if:
// 1. The payment request was rejected by approver/reviewer AND the current user is the creator, OR
// 2. The payment request is in any other status (not rejected)
var isRejected = paymentRequest.ExpenseStatusId == RejectedByApprover
|| paymentRequest.ExpenseStatusId == RejectedByReviewer;
if ((!isRejected) || (isRejected && (loggedInEmployee.Id == paymentRequest.CreatedById || hasManagePermission)))
{
response.NextStatus = nextStatuses;
}
response.UpdateLogs = updateLogs.Select(ul =>
{
var statusVm = status.FirstOrDefault(es => es.Id == ul.StatusId);
var nextStatusVm = status.FirstOrDefault(es => es.Id == ul.NextStatusId);
return new PaymentRequestUpdateLog
{
Id = ul.Id,
Comment = ul.Comment,
Status = _mapper.Map<ExpensesStatusMasterVM>(statusVm),
NextStatus = _mapper.Map<ExpensesStatusMasterVM>(nextStatusVm),
UpdatedAt = ul.UpdatedAt,
UpdatedBy = _mapper.Map<BasicEmployeeVM>(ul.UpdatedBy)
};
}).ToList();
_logger.LogInfo("Payment request details fetched successfully for PaymentRequestId: {PaymentRequestId}, EmployeeId: {EmployeeId}", paymentRequest.Id, loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(response, "Payment request fetched successfully.", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in GetPaymentRequestDetailsAsync for TenantId={TenantId}, EmployeeId={EmployeeId}: {Message}", tenantId, loggedInEmployee.Id, ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred while fetching the payment request details.", ex.Message, 500);
}
finally
{
_logger.LogInfo("End GetPaymentRequestDetailsAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
#endregion #endregion
#region =================================================================== Payment Request Functions =================================================================== #region =================================================================== Payment Request Functions ===================================================================

View File

@ -19,6 +19,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
#region =================================================================== Payment Request Functions =================================================================== #region =================================================================== Payment Request Functions ===================================================================
Task<ApiResponse<object>> GetPaymentRequestListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId); Task<ApiResponse<object>> GetPaymentRequestListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetPaymentRequestDetailsAsync(Guid? id, string? paymentRequestUId, Employee loggedInEmployee, Guid tenantId);
#endregion #endregion
#region =================================================================== Payment Request Functions =================================================================== #region =================================================================== Payment Request Functions ===================================================================