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
|
#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);
|
||||||
|
|
||||||
var expenseCategoryTask = Task.Run(async () =>
|
string uIDPrefix = $"PY/{DateTime.Now:MMyy}";
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
// Execute database lookups concurrently
|
||||||
return await context.ExpenseCategoryMasters.FirstOrDefaultAsync(et => et.Id == model.ExpenseCategoryId && et.IsActive);
|
var expenseCategoryTask = Task.Run(async () =>
|
||||||
});
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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.
|
_logger.LogInfo("Payment request created successfully with UID: {PaymentRequestUID}", response.PaymentRequestUID);
|
||||||
var processingTasks = attachments.Select(attachment =>
|
return ApiResponse<object>.SuccessResponse(response, "Created the Payment Request Successfully.", 201);
|
||||||
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)
|
}
|
||||||
).ToList();
|
catch (ArgumentException ex)
|
||||||
|
{
|
||||||
var results = await Task.WhenAll(processingTasks);
|
_logger.LogError(ex, "Argument error in CreatePaymentRequestAsync: {Message}", ex.Message);
|
||||||
|
return ApiResponse<object>.ErrorResponse(ex.Message, "Invalid data.", 400);
|
||||||
// This part is thread-safe as it runs after all concurrent tasks are complete.
|
}
|
||||||
foreach (var (document, paymentRequestAttachment) in results)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_context.Documents.Add(document);
|
_logger.LogError(ex, "Unexpected error in CreatePaymentRequestAsync: {Message}", ex.Message);
|
||||||
_context.PaymentRequestAttachments.Add(paymentRequestAttachment);
|
return ApiResponse<object>.ErrorResponse("An error occurred while creating the payment request.", ex.Message, 500);
|
||||||
}
|
}
|
||||||
_logger.LogInfo("{AttachmentCount} attachments processed and staged for saving.", results.Length);
|
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
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Payment Request Functions ===================================================================
|
#region =================================================================== Payment Request Functions ===================================================================
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user