Completed the Create payment request API
This commit is contained in:
parent
e1d6434fc6
commit
eccb87ebb4
@ -1084,115 +1084,148 @@ 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);
|
||||
|
||||
var expenseCategoryTask = Task.Run(async () =>
|
||||
string uIDPrefix = $"PY/{DateTime.Now:MMyy}";
|
||||
|
||||
try
|
||||
{
|
||||
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 = expenseCategoryTask.Result;
|
||||
|
||||
if (expenseCategory == null)
|
||||
{
|
||||
return ApiResponse<object>.ErrorResponse("Expense Category not found", "Expense Category not found", 404);
|
||||
}
|
||||
|
||||
var currency = currencyTask.Result;
|
||||
|
||||
if (currency == null)
|
||||
{
|
||||
return ApiResponse<object>.ErrorResponse("Currency not found", "Currency not found", 404);
|
||||
}
|
||||
|
||||
var expenseStatus = expenseStatusTask.Result;
|
||||
|
||||
if (expenseCategory == null)
|
||||
{
|
||||
return ApiResponse<object>.ErrorResponse("Expense Status 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();
|
||||
|
||||
int uIDPostfix = lastPR == null ? 1 : (lastPR.UIDPostfix + 1);
|
||||
|
||||
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;
|
||||
|
||||
_context.PaymentRequests.Add(paymentRequest);
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 4. Process Attachments
|
||||
if (model.BillAttachments?.Any() ?? false)
|
||||
{
|
||||
var attachments = model.BillAttachments;
|
||||
// Pre-validate all attachments to fail fast before any uploads.
|
||||
foreach (var attachment in attachments)
|
||||
// Execute database lookups concurrently
|
||||
var expenseCategoryTask = Task.Run(async () =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(attachment.Base64Data) || !_s3Service.IsBase64String(attachment.Base64Data))
|
||||
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);
|
||||
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)
|
||||
{
|
||||
throw new ArgumentException($"Invalid or missing Base64 data for attachment: {attachment.FileName ?? "N/A"}");
|
||||
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);
|
||||
}
|
||||
|
||||
var batchId = Guid.NewGuid();
|
||||
// Prepare response VM
|
||||
var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
|
||||
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);
|
||||
|
||||
// Create a list of tasks to be executed concurrently.
|
||||
var processingTasks = attachments.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.
|
||||
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);
|
||||
_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);
|
||||
}
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
|
||||
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix.ToString("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);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Payment Request Functions ===================================================================
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user