Added the API to get details of payment request
This commit is contained in:
parent
90b7cabaf5
commit
c5adc6322d
@ -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 ===================================================================
|
||||||
|
|
||||||
|
|||||||
@ -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 ===================================================================
|
||||||
|
|
||||||
|
|||||||
@ -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 ===================================================================
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user