Completed the Create payment request API

This commit is contained in:
ashutosh.nehete 2025-11-01 16:07:47 +05:30
parent e1d6434fc6
commit eccb87ebb4

View File

@ -1084,10 +1084,16 @@ namespace Marco.Pms.Services.Service
#endregion #endregion
#region =================================================================== Payment Request Functions =================================================================== #region =================================================================== Payment Request Functions ===================================================================
public async Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId) public async Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId)
{ {
string uIDPrefix = $"PY/{DateTime.Now.ToString("MMyy")}"; _logger.LogInfo("Start CreatePaymentRequestAsync for EmployeeId: {EmployeeId} TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
string uIDPrefix = $"PY/{DateTime.Now:MMyy}";
try
{
// Execute database lookups concurrently
var expenseCategoryTask = Task.Run(async () => var expenseCategoryTask = Task.Run(async () =>
{ {
await using var context = await _dbContextFactory.CreateDbContextAsync(); await using var context = await _dbContextFactory.CreateDbContextAsync();
@ -1111,33 +1117,37 @@ namespace Marco.Pms.Services.Service
await Task.WhenAll(expenseCategoryTask, currencyTask, expenseStatusTask, projectTask); await Task.WhenAll(expenseCategoryTask, currencyTask, expenseStatusTask, projectTask);
var expenseCategory = expenseCategoryTask.Result; var expenseCategory = await expenseCategoryTask;
if (expenseCategory == null) if (expenseCategory == null)
{ {
return ApiResponse<object>.ErrorResponse("Expense Category not found", "Expense Category not found", 404); _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 = currencyTask.Result; var currency = await currencyTask;
if (currency == null) if (currency == null)
{ {
return ApiResponse<object>.ErrorResponse("Currency not found", "Currency not found", 404); _logger.LogWarning("Currency not found with Id: {CurrencyId}", model.CurrencyId);
return ApiResponse<object>.ErrorResponse("Currency not found.", "Currency not found.", 404);
} }
var expenseStatus = expenseStatusTask.Result; var expenseStatus = await expenseStatusTask;
if (expenseStatus == null)
if (expenseCategory == null)
{ {
return ApiResponse<object>.ErrorResponse("Expense Status not found", "Expense Status not found", 404); _logger.LogWarning("Expense Status not found for Draft.");
return ApiResponse<object>.ErrorResponse("Expense Status (Draft) not found.", "Expense Status not found.", 404);
} }
var project = projectTask.Result; var project = await projectTask;
// Project is optional so no error if not found
var lastPR = await _context.PaymentRequests.Where(pr => pr.UIDPrefix == uIDPrefix).OrderByDescending(pr => pr.UIDPostfix).FirstOrDefaultAsync();
// 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); int uIDPostfix = lastPR == null ? 1 : (lastPR.UIDPostfix + 1);
// Map DTO to PaymentRequest entity and set additional mandatory fields
var paymentRequest = _mapper.Map<PaymentRequest>(model); var paymentRequest = _mapper.Map<PaymentRequest>(model);
paymentRequest.ExpenseStatusId = Draft; paymentRequest.ExpenseStatusId = Draft;
paymentRequest.UIDPrefix = uIDPrefix; paymentRequest.UIDPrefix = uIDPrefix;
@ -1147,52 +1157,75 @@ namespace Marco.Pms.Services.Service
paymentRequest.CreatedById = loggedInEmployee.Id; paymentRequest.CreatedById = loggedInEmployee.Id;
paymentRequest.TenantId = tenantId; paymentRequest.TenantId = tenantId;
_context.PaymentRequests.Add(paymentRequest); await _context.PaymentRequests.AddAsync(paymentRequest);
await _context.SaveChangesAsync(); await _context.SaveChangesAsync();
// 4. Process Attachments // Process bill attachments if any
if (model.BillAttachments?.Any() ?? false) if (model.BillAttachments?.Any() == true)
{ {
var attachments = model.BillAttachments; _logger.LogInfo("Processing {AttachmentCount} attachments for PaymentRequest Id: {PaymentRequestId}",
// Pre-validate all attachments to fail fast before any uploads. model.BillAttachments.Count, paymentRequest.Id);
foreach (var attachment in attachments)
// Validate base64 attachments data before processing
foreach (var attachment in model.BillAttachments)
{ {
if (string.IsNullOrWhiteSpace(attachment.Base64Data) || !_s3Service.IsBase64String(attachment.Base64Data)) 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"}"); throw new ArgumentException($"Invalid or missing Base64 data for attachment: {attachment.FileName ?? "N/A"}");
} }
} }
var batchId = Guid.NewGuid(); var batchId = Guid.NewGuid();
// Create a list of tasks to be executed concurrently. // Process all attachments concurrently
var processingTasks = attachments.Select(attachment => var processingTasks = model.BillAttachments.Select(attachment =>
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId) ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)
).ToList(); ).ToList();
var results = await Task.WhenAll(processingTasks); var results = await Task.WhenAll(processingTasks);
// This part is thread-safe as it runs after all concurrent tasks are complete. // Add documents and payment request attachments after concurrent processing
foreach (var (document, paymentRequestAttachment) in results) foreach (var (document, paymentRequestAttachment) in results)
{ {
_context.Documents.Add(document); _context.Documents.Add(document);
_context.PaymentRequestAttachments.Add(paymentRequestAttachment); _context.PaymentRequestAttachments.Add(paymentRequestAttachment);
} }
_logger.LogInfo("{AttachmentCount} attachments processed and staged for saving.", results.Length);
}
await _context.SaveChangesAsync(); 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); var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix.ToString("D5")}"; response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
response.Currency = currency; response.Currency = currency;
response.ExpenseCategory = _mapper.Map<ExpensesTypeMasterVM>(expenseCategory); response.ExpenseCategory = _mapper.Map<ExpensesTypeMasterVM>(expenseCategory);
response.ExpenseStatus = _mapper.Map<ExpensesStatusMasterVM>(expenseStatus); response.ExpenseStatus = _mapper.Map<ExpensesStatusMasterVM>(expenseStatus);
response.Project = _mapper.Map<BasicProjectVM>(project); response.Project = _mapper.Map<BasicProjectVM>(project);
response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee); response.CreatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
return ApiResponse<object>.SuccessResponse(response, "Created the Payment Request Successfully", 201); _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 #endregion
#region =================================================================== Payment Request Functions =================================================================== #region =================================================================== Payment Request Functions ===================================================================