Added the edit payment request API
This commit is contained in:
parent
b6295ebef4
commit
a04ef7ca9b
@ -138,6 +138,18 @@ namespace Marco.Pms.Services.Controllers
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
[HttpPut("payment-request/edit/{id}")]
|
||||
public async Task<IActionResult> EditPaymentRequest(Guid id, [FromBody] PaymentRequestDto model)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _expensesService.EditPaymentRequestAsync(id, 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -1226,6 +1226,197 @@ namespace Marco.Pms.Services.Service
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<object>> EditPaymentRequestAsync(Guid id, PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("Start EditPaymentRequestAsync for PaymentRequestId: {PaymentRequestId}, EmployeeId: {EmployeeId}", id, loggedInEmployee.Id);
|
||||
|
||||
if (model.Id == null || id != model.Id)
|
||||
{
|
||||
_logger.LogWarning("Mismatch between URL id and payload id: {Id} vs {ModelId}", id, model?.Id ?? Guid.Empty);
|
||||
return ApiResponse<object>.ErrorResponse("Invalid argument: ID mismatch.", "Invalid argument: ID mismatch.", 400);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Concurrently fetch related entities to validate input references
|
||||
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 projectTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return model.ProjectId.HasValue ? await context.Projects.FirstOrDefaultAsync(p => p.Id == model.ProjectId.Value) : null;
|
||||
});
|
||||
|
||||
await Task.WhenAll(expenseCategoryTask, currencyTask, 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 project = await projectTask; // Project can be null (optional)
|
||||
|
||||
// Retrieve the existing payment request with relevant navigation properties for validation and mapping
|
||||
var paymentRequest = await _context.PaymentRequests
|
||||
.Include(pr => pr.ExpenseCategory)
|
||||
.Include(pr => pr.Project)
|
||||
.Include(pr => pr.ExpenseStatus)
|
||||
.Include(pr => pr.RecurringPayment)
|
||||
.Include(pr => pr.Currency)
|
||||
.Include(pr => pr.CreatedBy).ThenInclude(e => e!.JobRole)
|
||||
.Include(pr => pr.UpdatedBy).ThenInclude(e => e!.JobRole)
|
||||
.FirstOrDefaultAsync(pr => pr.Id == id);
|
||||
|
||||
if (paymentRequest == null)
|
||||
{
|
||||
_logger.LogWarning("Payment Request not found with Id: {PaymentRequestId}", id);
|
||||
return ApiResponse<object>.ErrorResponse("Payment Request not found.", "Payment Request not found.", 404);
|
||||
}
|
||||
|
||||
// Check if status prevents editing (only allow edit if status Draft, RejectedByReviewer, or RejectedByApprover)
|
||||
bool statusCheck = paymentRequest.ExpenseStatusId != Draft &&
|
||||
paymentRequest.ExpenseStatusId != RejectedByReviewer &&
|
||||
paymentRequest.ExpenseStatusId != RejectedByApprover;
|
||||
|
||||
bool isVariableRecurring = paymentRequest.RecurringPayment?.IsVariable ?? false;
|
||||
|
||||
// Capture existing state for auditing
|
||||
var existingEntityBson = _updateLogHelper.EntityToBsonDocument(paymentRequest);
|
||||
|
||||
// Only map updates if allowed by status
|
||||
if (!statusCheck && paymentRequest.CreatedById == loggedInEmployee.Id)
|
||||
{
|
||||
_mapper.Map(model, paymentRequest);
|
||||
paymentRequest.UpdatedAt = DateTime.UtcNow;
|
||||
paymentRequest.UpdatedById = loggedInEmployee.Id;
|
||||
}
|
||||
|
||||
if (isVariableRecurring)
|
||||
{
|
||||
paymentRequest.Amount = model.Amount;
|
||||
}
|
||||
|
||||
paymentRequest.IsAdvancePayment = model.IsAdvancePayment;
|
||||
|
||||
// Handle bill attachment updates: add new attachments and delete deactivated ones
|
||||
if (model.BillAttachments?.Any() == true)
|
||||
{
|
||||
var newBillAttachments = model.BillAttachments.Where(ba => ba.DocumentId == null && ba.IsActive).ToList();
|
||||
if (newBillAttachments.Any())
|
||||
{
|
||||
_logger.LogInfo("Processing {AttachmentCount} new attachments for PaymentRequest Id: {PaymentRequestId}", newBillAttachments.Count, paymentRequest.Id);
|
||||
|
||||
// Pre-validate base64 data before upload
|
||||
foreach (var attachment in newBillAttachments)
|
||||
{
|
||||
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();
|
||||
|
||||
var processingTasks = newBillAttachments.Select(attachment =>
|
||||
ProcessSinglePaymentRequestAttachmentAsync(attachment, paymentRequest, loggedInEmployee.Id, tenantId, batchId)).ToList();
|
||||
|
||||
var results = await Task.WhenAll(processingTasks);
|
||||
|
||||
foreach (var (document, paymentRequestAttachment) in results)
|
||||
{
|
||||
_context.Documents.Add(document);
|
||||
_context.PaymentRequestAttachments.Add(paymentRequestAttachment);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInfo("Added {Count} new attachments for PaymentRequest {PaymentRequestId} by Employee {EmployeeId}", newBillAttachments.Count, paymentRequest.Id, loggedInEmployee.Id);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
_logger.LogError(dbEx, "Database Exception while adding attachments during PaymentRequest update");
|
||||
return ApiResponse<object>.ErrorResponse("Database exception during attachment addition.", ExceptionMapper(dbEx), 500);
|
||||
}
|
||||
}
|
||||
|
||||
var deleteBillAttachments = model.BillAttachments.Where(ba => ba.DocumentId != null && !ba.IsActive).ToList();
|
||||
if (deleteBillAttachments.Any())
|
||||
{
|
||||
var documentIds = deleteBillAttachments.Select(d => d.DocumentId!.Value).ToList();
|
||||
|
||||
try
|
||||
{
|
||||
await DeletePaymentRequestAttachemnts(documentIds);
|
||||
_logger.LogInfo("Deleted {Count} attachments for PaymentRequest {PaymentRequestId} by Employee {EmployeeId}", deleteBillAttachments.Count, paymentRequest.Id, loggedInEmployee.Id);
|
||||
}
|
||||
catch (DbUpdateException dbEx)
|
||||
{
|
||||
_logger.LogError(dbEx, "Database Exception while deleting attachments during PaymentRequest update");
|
||||
return ApiResponse<object>.ErrorResponse("Database exception during attachment deletion.", ExceptionMapper(dbEx), 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected exception while deleting attachments during PaymentRequest update");
|
||||
return ApiResponse<object>.ErrorResponse("Error occurred while deleting attachments.", ExceptionMapper(ex), 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log the update audit trail
|
||||
await _updateLogHelper.PushToUpdateLogsAsync(new UpdateLogsObject
|
||||
{
|
||||
EntityId = paymentRequest.Id.ToString(),
|
||||
UpdatedById = loggedInEmployee.Id.ToString(),
|
||||
OldObject = existingEntityBson,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
}, "PaymentRequestModificationLog");
|
||||
|
||||
// Prepare response view model with updated details
|
||||
var response = _mapper.Map<PaymentRequestVM>(paymentRequest);
|
||||
response.PaymentRequestUID = $"{paymentRequest.UIDPrefix}/{paymentRequest.UIDPostfix:D5}";
|
||||
response.Currency = currency;
|
||||
response.ExpenseCategory = _mapper.Map<ExpensesTypeMasterVM>(expenseCategory);
|
||||
response.Project = _mapper.Map<BasicProjectVM>(project);
|
||||
response.UpdatedBy = _mapper.Map<BasicEmployeeVM>(loggedInEmployee);
|
||||
|
||||
_logger.LogInfo("PaymentRequest updated successfully with UID: {PaymentRequestUID}", response.PaymentRequestUID);
|
||||
return ApiResponse<object>.SuccessResponse(response, "Payment Request updated successfully.", 200);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Argument error in EditPaymentRequestAsync: {Message}", ex.Message);
|
||||
return ApiResponse<object>.ErrorResponse(ex.Message, "Invalid data.", 400);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error in EditPaymentRequestAsync: {Message}", ex.Message);
|
||||
return ApiResponse<object>.ErrorResponse("An error occurred while updating the payment request.", ex.Message, 500);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInfo("End EditPaymentRequestAsync for PaymentRequestId: {PaymentRequestId}", id);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region =================================================================== Payment Request Functions ===================================================================
|
||||
@ -1660,6 +1851,47 @@ namespace Marco.Pms.Services.Service
|
||||
|
||||
await Task.WhenAll(attachmentTask, documentsTask);
|
||||
}
|
||||
private async Task DeletePaymentRequestAttachemnts(List<Guid> documentIds)
|
||||
{
|
||||
var attachmentTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
var attachments = await dbContext.PaymentRequestAttachments.AsNoTracking().Where(ba => documentIds.Contains(ba.DocumentId)).ToListAsync();
|
||||
|
||||
dbContext.PaymentRequestAttachments.RemoveRange(attachments);
|
||||
await dbContext.SaveChangesAsync();
|
||||
});
|
||||
var documentsTask = Task.Run(async () =>
|
||||
{
|
||||
await using var dbContext = await _dbContextFactory.CreateDbContextAsync();
|
||||
var documents = await dbContext.Documents.AsNoTracking().Where(ba => documentIds.Contains(ba.Id)).ToListAsync();
|
||||
|
||||
if (documents.Any())
|
||||
{
|
||||
dbContext.Documents.RemoveRange(documents);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
List<S3DeletionObject> deletionObject = new List<S3DeletionObject>();
|
||||
foreach (var document in documents)
|
||||
{
|
||||
deletionObject.Add(new S3DeletionObject
|
||||
{
|
||||
Key = document.S3Key
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(document.ThumbS3Key) && document.ThumbS3Key != document.S3Key)
|
||||
{
|
||||
deletionObject.Add(new S3DeletionObject
|
||||
{
|
||||
Key = document.ThumbS3Key
|
||||
});
|
||||
}
|
||||
}
|
||||
await _updateLogHelper.PushToS3DeletionAsync(deletionObject);
|
||||
}
|
||||
});
|
||||
|
||||
await Task.WhenAll(attachmentTask, documentsTask);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@ -17,5 +17,6 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
|
||||
|
||||
Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> EditPaymentRequestAsync(Guid id, PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user