Added the API to create the payment request
This commit is contained in:
parent
eccd4f6d76
commit
b54b83c63d
@ -157,6 +157,18 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
var response = await _expensesService.GetPaymentRequestFilterObjectAsync(loggedInEmployee, tenantId);
|
var response = await _expensesService.GetPaymentRequestFilterObjectAsync(loggedInEmployee, tenantId);
|
||||||
return StatusCode(response.StatusCode, response);
|
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
|
#endregion
|
||||||
#region =================================================================== Payment Request Functions ===================================================================
|
#region =================================================================== Payment Request Functions ===================================================================
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using Marco.Pms.Model.Employees;
|
|||||||
using Marco.Pms.Model.Entitlements;
|
using Marco.Pms.Model.Entitlements;
|
||||||
using Marco.Pms.Model.Expenses;
|
using Marco.Pms.Model.Expenses;
|
||||||
using Marco.Pms.Model.Filters;
|
using Marco.Pms.Model.Filters;
|
||||||
|
using Marco.Pms.Model.Master;
|
||||||
using Marco.Pms.Model.MongoDBModels;
|
using Marco.Pms.Model.MongoDBModels;
|
||||||
using Marco.Pms.Model.MongoDBModels.Employees;
|
using Marco.Pms.Model.MongoDBModels.Employees;
|
||||||
using Marco.Pms.Model.MongoDBModels.Expenses;
|
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);
|
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
|
#endregion
|
||||||
#region =================================================================== Payment Request Functions ===================================================================
|
#region =================================================================== Payment Request Functions ===================================================================
|
||||||
|
|||||||
@ -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>> GetPaymentRequestDetailsAsync(Guid? id, string? paymentRequestUId, Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> GetPayeeNameListAsync(Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> GetPayeeNameListAsync(Employee loggedInEmployee, Guid tenantId);
|
||||||
Task<ApiResponse<object>> GetPaymentRequestFilterObjectAsync(Employee loggedInEmployee, Guid tenantId);
|
Task<ApiResponse<object>> GetPaymentRequestFilterObjectAsync(Employee loggedInEmployee, Guid tenantId);
|
||||||
|
Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId);
|
||||||
#endregion
|
#endregion
|
||||||
#region =================================================================== Payment Request Functions ===================================================================
|
#region =================================================================== Payment Request Functions ===================================================================
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user