Added the API to create the payment request

This commit is contained in:
ashutosh.nehete 2025-11-07 17:36:11 +05:30
parent eccd4f6d76
commit b54b83c63d
3 changed files with 178 additions and 0 deletions

View File

@ -157,6 +157,18 @@ namespace Marco.Pms.Services.Controllers
var response = await _expensesService.GetPaymentRequestFilterObjectAsync(loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response);
}
[HttpPost("payment-request/create")]
public async Task<IActionResult> CreatePaymentRequest([FromBody] PaymentRequestDto model)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.CreatePaymentRequestAsync(model, loggedInEmployee, tenantId);
if (response.Success)
{
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Payment_Request", Response = response.Data };
await _signalR.SendNotificationAsync(notification);
}
return StatusCode(response.StatusCode, response);
}
#endregion
#region =================================================================== Payment Request Functions ===================================================================

View File

@ -7,6 +7,7 @@ using Marco.Pms.Model.Employees;
using Marco.Pms.Model.Entitlements;
using Marco.Pms.Model.Expenses;
using Marco.Pms.Model.Filters;
using Marco.Pms.Model.Master;
using Marco.Pms.Model.MongoDBModels;
using Marco.Pms.Model.MongoDBModels.Employees;
using Marco.Pms.Model.MongoDBModels.Expenses;
@ -1654,6 +1655,170 @@ namespace Marco.Pms.Services.Service
return ApiResponse<object>.ErrorResponse("Internal Exception Occured", ExceptionMapper(ex), 500);
}
}
public async Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start CreatePaymentRequestAsync for EmployeeId: {EmployeeId} TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
string uIDPrefix = $"PR/{DateTime.Now:MMyy}";
try
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
var hasUploadPermission = await permissionService.HasPermission(PermissionsMaster.ExpenseUpload, loggedInEmployee.Id);
if (!hasUploadPermission)
{
_logger.LogWarning("Access DENIED: Employee {EmployeeId} has no permission to create payment requests.", loggedInEmployee.Id);
return ApiResponse<object>.ErrorResponse("Access Denied", "You do not have permission to create any payment request.", 409);
}
// Execute database lookups concurrently
var expenseCategoryTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ExpenseCategoryMasters.FirstOrDefaultAsync(et => et.Id == model.ExpenseCategoryId && et.IsActive);
});
var currencyTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.CurrencyMaster.FirstOrDefaultAsync(c => c.Id == model.CurrencyId);
});
var expenseStatusTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.ExpensesStatusMaster.FirstOrDefaultAsync(es => es.Id == Draft && es.IsActive);
});
var projectTask = Task.Run(async () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
return await context.Projects.FirstOrDefaultAsync(P => model.ProjectId.HasValue && P.Id == model.ProjectId.Value);
});
await Task.WhenAll(expenseCategoryTask, currencyTask, expenseStatusTask, projectTask);
var expenseCategory = await expenseCategoryTask;
if (expenseCategory == null)
{
_logger.LogWarning("Expense Category not found with Id: {ExpenseCategoryId}", model.ExpenseCategoryId);
return ApiResponse<object>.ErrorResponse("Expense Category not found.", "Expense Category not found.", 404);
}
var currency = await currencyTask;
if (currency == null)
{
_logger.LogWarning("Currency not found with Id: {CurrencyId}", model.CurrencyId);
return ApiResponse<object>.ErrorResponse("Currency not found.", "Currency not found.", 404);
}
var expenseStatus = await expenseStatusTask;
if (expenseStatus == null)
{
_logger.LogWarning("Expense Status not found for Draft.");
return ApiResponse<object>.ErrorResponse("Expense Status (Draft) not found.", "Expense Status not found.", 404);
}
var project = await projectTask;
// Project is optional so no error if not found
// Generate unique UID postfix based on existing requests for the current prefix
var lastPR = await _context.PaymentRequests.Where(pr => pr.UIDPrefix == uIDPrefix)
.OrderByDescending(pr => pr.UIDPostfix)
.FirstOrDefaultAsync();
int uIDPostfix = lastPR == null ? 1 : (lastPR.UIDPostfix + 1);
// Map DTO to PaymentRequest entity and set additional mandatory fields
var paymentRequest = _mapper.Map<PaymentRequest>(model);
paymentRequest.ExpenseStatusId = Draft;
paymentRequest.UIDPrefix = uIDPrefix;
paymentRequest.UIDPostfix = uIDPostfix;
paymentRequest.IsActive = true;
paymentRequest.CreatedAt = DateTime.UtcNow;
paymentRequest.CreatedById = loggedInEmployee.Id;
paymentRequest.TenantId = tenantId;
await _context.PaymentRequests.AddAsync(paymentRequest);
// 8. Add paymentRequest Log Entry
_context.StatusUpdateLogs.Add(new StatusUpdateLog
{
Id = Guid.NewGuid(),
EntityId = paymentRequest.Id,
StatusId = Draft,
NextStatusId = Draft,
UpdatedById = loggedInEmployee.Id,
UpdatedAt = DateTime.UtcNow,
Comment = "Payment request is submited as draft",
TenantId = tenantId
});
await _context.SaveChangesAsync();
// Process bill attachments if any
if (model.BillAttachments?.Any() == true)
{
_logger.LogInfo("Processing {AttachmentCount} attachments for PaymentRequest Id: {PaymentRequestId}",
model.BillAttachments.Count, paymentRequest.Id);
// Validate base64 attachments data before processing
foreach (var attachment in model.BillAttachments)
{
if (string.IsNullOrWhiteSpace(attachment.Base64Data) || !_s3Service.IsBase64String(attachment.Base64Data))
{
_logger.LogWarning("Invalid or missing Base64 data for attachment: {FileName}", attachment.FileName ?? "N/A");
throw new ArgumentException($"Invalid or missing Base64 data for attachment: {attachment.FileName ?? "N/A"}");
}
}
var batchId = Guid.NewGuid();
// Process all attachments concurrently
var processingTasks = model.BillAttachments.Select(attachment =>
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)
).ToList();
var results = await Task.WhenAll(processingTasks);
// Add documents and payment request attachments after concurrent processing
foreach (var (document, paymentRequestAttachment) in results)
{
_context.Documents.Add(document);
_context.PaymentRequestAttachments.Add(paymentRequestAttachment);
}
await _context.SaveChangesAsync();
_logger.LogInfo("{AttachmentCount} attachments processed and saved for PaymentRequest Id: {PaymentRequestId}",
results.Length, paymentRequest.Id);
}
// Prepare response VM
var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
response.Currency = currency;
response.ExpenseCategory = _mapper.Map<ExpenseCategoryMasterVM>(expenseCategory);
response.ExpenseStatus = _mapper.Map<ExpensesStatusMasterVM>(expenseStatus);
response.Project = _mapper.Map<BasicProjectVM>(project);
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
_logger.LogInfo("Payment request created successfully with UID: {PaymentRequestUID}", response.PaymentRequestUID);
return ApiResponse<object>.SuccessResponse(response, "Created the Payment Request Successfully.", 201);
}
catch (ArgumentException ex)
{
_logger.LogError(ex, "Argument error in CreatePaymentRequestAsync: {Message}", ex.Message);
return ApiResponse<object>.ErrorResponse(ex.Message, "Invalid data.", 400);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error in CreatePaymentRequestAsync: {Message}", ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred while creating the payment request.", ex.Message, 500);
}
finally
{
_logger.LogInfo("End CreatePaymentRequestAsync for EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
#endregion
#region =================================================================== Payment Request Functions ===================================================================

View File

@ -22,6 +22,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
Task<ApiResponse<object>> GetPaymentRequestDetailsAsync(Guid? id, string? paymentRequestUId, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetPayeeNameListAsync(Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetPaymentRequestFilterObjectAsync(Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId);
#endregion
#region =================================================================== Payment Request Functions ===================================================================