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
#region =================================================================== Payment Request Functions ===================================================================
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 () =>
{
await using var context = await _dbContextFactory.CreateDbContextAsync();
@ -1111,33 +1117,37 @@ namespace Marco.Pms.Services.Service
await Task.WhenAll(expenseCategoryTask, currencyTask, expenseStatusTask, projectTask);
var expenseCategory = expenseCategoryTask.Result;
var expenseCategory = await expenseCategoryTask;
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)
{
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;
if (expenseCategory == null)
var expenseStatus = await expenseStatusTask;
if (expenseStatus == 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 lastPR = await _context.PaymentRequests.Where(pr => pr.UIDPrefix == uIDPrefix).OrderByDescending(pr => pr.UIDPostfix).FirstOrDefaultAsync();
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;
@ -1147,52 +1157,75 @@ namespace Marco.Pms.Services.Service
paymentRequest.CreatedById = loggedInEmployee.Id;
paymentRequest.TenantId = tenantId;
_context.PaymentRequests.Add(paymentRequest);
await _context.PaymentRequests.AddAsync(paymentRequest);
await _context.SaveChangesAsync();
// 4. Process Attachments
if (model.BillAttachments?.Any() ?? false)
// Process bill attachments if any
if (model.BillAttachments?.Any() == true)
{
var attachments = model.BillAttachments;
// Pre-validate all attachments to fail fast before any uploads.
foreach (var attachment in attachments)
_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();
// Create a list of tasks to be executed concurrently.
var processingTasks = attachments.Select(attachment =>
// Process all attachments concurrently
var processingTasks = model.BillAttachments.Select(attachment =>
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)
).ToList();
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)
{
_context.Documents.Add(document);
_context.PaymentRequestAttachments.Add(paymentRequestAttachment);
}
_logger.LogInfo("{AttachmentCount} attachments processed and staged for saving.", results.Length);
}
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.ToString("D5")}";
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
response.Currency = currency;
response.ExpenseCategory = _mapper.Map<ExpensesTypeMasterVM>(expenseCategory);
response.ExpenseStatus = _mapper.Map<ExpensesStatusMasterVM>(expenseStatus);
response.Project = _mapper.Map<BasicProjectVM>(project);
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
#region =================================================================== Payment Request Functions ===================================================================