Added the API to create expense from payment request
This commit is contained in:
parent
96411c43b0
commit
73b85bee84
@ -184,6 +184,19 @@ namespace Marco.Pms.Services.Controllers
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
[HttpPost("payment-request/expense/create")]
|
||||
public async Task<IActionResult> ChangeToExpanseFromPaymentRequest(ExpenseConversionDto model)
|
||||
{
|
||||
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var response = await _expensesService.ChangeToExpanseFromPaymentRequestAsync(model, loggedInEmployee, tenantId);
|
||||
if (response.Success)
|
||||
{
|
||||
var notification = new { LoggedInUserId = loggedInEmployee.Id, Keyword = "Expanse", Response = response.Data };
|
||||
await _signalR.SendNotificationAsync(notification);
|
||||
}
|
||||
return StatusCode(response.StatusCode, response);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Payment Request Functions ===================================================================
|
||||
|
||||
|
||||
@ -2023,6 +2023,174 @@ namespace Marco.Pms.Services.Service
|
||||
return ApiResponse<object>.SuccessResponse(responseDto, "Status updated, but audit logging or cache update failed.");
|
||||
}
|
||||
}
|
||||
public async Task<ApiResponse<object>> ChangeToExpanseFromPaymentRequestAsync(ExpenseConversionDto model, Employee loggedInEmployee, Guid tenantId)
|
||||
{
|
||||
_logger.LogInfo("Start ChangeToExpanseFromPaymentRequestAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} PaymentRequestId: {PaymentRequestId}",
|
||||
loggedInEmployee.Id, tenantId, model.PaymentRequestId);
|
||||
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// Retrieve payment request with required navigation property and validation
|
||||
var paymentRequest = await _context.PaymentRequests
|
||||
.Include(pr => pr.ExpenseCategory)
|
||||
.FirstOrDefaultAsync(pr =>
|
||||
pr.Id == model.PaymentRequestId &&
|
||||
!pr.IsAdvancePayment &&
|
||||
pr.ProjectId.HasValue &&
|
||||
pr.ExpenseCategoryId.HasValue &&
|
||||
pr.PaidById.HasValue &&
|
||||
pr.PaidAt.HasValue &&
|
||||
pr.ExpenseCategory != null &&
|
||||
pr.TenantId == tenantId &&
|
||||
pr.IsActive);
|
||||
|
||||
if (paymentRequest == null)
|
||||
{
|
||||
_logger.LogWarning("Payment request not found for Id: {PaymentRequestId}, TenantId: {TenantId}", model.PaymentRequestId, tenantId);
|
||||
return ApiResponse<object>.ErrorResponse("Payment request not found.", "Payment request not found.", 404);
|
||||
}
|
||||
|
||||
// Check payment request status for eligibility to convert
|
||||
if (paymentRequest.ExpenseStatusId != Processed)
|
||||
{
|
||||
_logger.LogWarning("Payment request {PaymentRequestId} status is not processed. Current status: {StatusId}", paymentRequest.Id, paymentRequest.ExpenseStatusId);
|
||||
return ApiResponse<object>.ErrorResponse("Payment is not processed.", "Payment is not processed.", 400);
|
||||
}
|
||||
|
||||
// Verify attachment requirements
|
||||
var hasAttachments = model.BillAttachments?.Any() ?? false;
|
||||
|
||||
if (!hasAttachments && paymentRequest.ExpenseCategory!.IsAttachmentRequried)
|
||||
{
|
||||
_logger.LogWarning("Attachment is required for ExpenseCategory {ExpenseCategoryId} but no attachments provided.", paymentRequest.ExpenseCategoryId!);
|
||||
return ApiResponse<object>.ErrorResponse("Attachment is required.", "Attachment is required.", 400);
|
||||
}
|
||||
|
||||
// Concurrently fetch status update logs and expense statuses required for logging and state transition
|
||||
var statusUpdateLogTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.StatusUpdateLogs
|
||||
.Where(sul => sul.EntityId == paymentRequest.Id && sul.TenantId == tenantId)
|
||||
.OrderByDescending(sul => sul.UpdatedAt)
|
||||
.ToListAsync();
|
||||
});
|
||||
|
||||
var expenseStatusTask = Task.Run(async () =>
|
||||
{
|
||||
await using var context = await _dbContextFactory.CreateDbContextAsync();
|
||||
return await context.ExpensesStatusMaster.ToListAsync();
|
||||
});
|
||||
|
||||
await Task.WhenAll(statusUpdateLogTask, expenseStatusTask);
|
||||
|
||||
var statusUpdateLogs = statusUpdateLogTask.Result;
|
||||
var expenseStatuses = expenseStatusTask.Result;
|
||||
|
||||
// Generate Expense UID with prefix for current period
|
||||
string uIDPrefix = $"EX/{DateTime.Now:MMyy}";
|
||||
|
||||
var lastExpense = await _context.Expenses
|
||||
.Where(e => e.UIDPrefix == uIDPrefix)
|
||||
.OrderByDescending(e => e.UIDPostfix)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
int uIDPostfix = lastExpense == null ? 1 : (lastExpense.UIDPostfix + 1);
|
||||
|
||||
// Get user IDs involved in review, approval, and processing from logs for audit trail linking
|
||||
var reviewedLog = statusUpdateLogs.FirstOrDefault(sul => sul.NextStatusId == Approve || sul.NextStatusId == RejectedByReviewer);
|
||||
var approvedLog = statusUpdateLogs.FirstOrDefault(sul => sul.NextStatusId == ProcessPending || sul.NextStatusId == RejectedByApprover);
|
||||
var processedLog = statusUpdateLogs.FirstOrDefault(sul => sul.NextStatusId == Processed);
|
||||
|
||||
// Create new Expense record replicating required data from PaymentRequest and input model
|
||||
var expense = new Expenses
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UIDPrefix = uIDPrefix,
|
||||
UIDPostfix = uIDPostfix,
|
||||
ProjectId = paymentRequest.ProjectId!.Value,
|
||||
ExpenseCategoryId = paymentRequest.ExpenseCategoryId!.Value,
|
||||
PaymentModeId = model.PaymentModeId,
|
||||
PaidById = paymentRequest.PaidById!.Value,
|
||||
CreatedById = loggedInEmployee.Id,
|
||||
ReviewedById = reviewedLog?.UpdatedById,
|
||||
ApprovedById = approvedLog?.UpdatedById,
|
||||
ProcessedById = processedLog?.UpdatedById,
|
||||
TransactionDate = paymentRequest.PaidAt!.Value,
|
||||
Description = paymentRequest.Description,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
TransactionId = paymentRequest.PaidTransactionId,
|
||||
Location = model.Location,
|
||||
GSTNumber = model.GSTNumber,
|
||||
SupplerName = paymentRequest.Payee,
|
||||
CurrencyId = paymentRequest.CurrencyId,
|
||||
Amount = paymentRequest.Amount,
|
||||
BaseAmount = paymentRequest.BaseAmount,
|
||||
TaxAmount = paymentRequest.TaxAmount,
|
||||
TDSPercentage = paymentRequest.TDSPercentage,
|
||||
PaymentRequestId = paymentRequest.Id,
|
||||
StatusId = Processed,
|
||||
PreApproved = false,
|
||||
IsActive = true,
|
||||
TenantId = tenantId
|
||||
};
|
||||
|
||||
_context.Expenses.Add(expense);
|
||||
|
||||
// Prepare ExpenseLog entries for each relevant previous status update log with reduced timestamp to preserve order
|
||||
var millisecondsOffset = 60;
|
||||
var expenseLogs = statusUpdateLogs
|
||||
.Where(sul => expenseStatuses.Any(es => es.Id == sul.NextStatusId))
|
||||
.Select(sul =>
|
||||
{
|
||||
var nextStatus = expenseStatuses.FirstOrDefault(es => es.Id == sul.NextStatusId);
|
||||
var log = new ExpenseLog
|
||||
{
|
||||
ExpenseId = expense.Id,
|
||||
Action = $"Status changed to '{nextStatus?.Name}'",
|
||||
UpdatedById = loggedInEmployee.Id,
|
||||
UpdateAt = DateTime.UtcNow.AddMilliseconds(millisecondsOffset),
|
||||
Comment = $"Status changed to '{nextStatus?.Name}'",
|
||||
TenantId = tenantId
|
||||
};
|
||||
millisecondsOffset -= 1;
|
||||
return log;
|
||||
}).ToList();
|
||||
|
||||
_context.ExpenseLogs.AddRange(expenseLogs);
|
||||
|
||||
// Process and upload bill attachments if present
|
||||
if (hasAttachments)
|
||||
{
|
||||
await ProcessAndUploadAttachmentsAsync(model.BillAttachments!, expense, loggedInEmployee.Id, tenantId);
|
||||
}
|
||||
|
||||
// Mark the payment request as converted to expense to prevent duplicates
|
||||
paymentRequest.IsExpenseCreated = true;
|
||||
|
||||
// Persist all changes within transaction to ensure atomicity
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
_logger.LogInfo("Expense converted successfully from PaymentRequestId: {PaymentRequestId} by EmployeeId: {EmployeeId}", paymentRequest.Id, loggedInEmployee.Id);
|
||||
|
||||
return ApiResponse<object>.SuccessResponse(model, "Expense created successfully.", 201);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in ChangeToExpanseFromPaymentRequestAsync for PaymentRequestId: {PaymentRequestId}, TenantId: {TenantId}, EmployeeId: {EmployeeId}: {Message}",
|
||||
model.PaymentRequestId, tenantId, loggedInEmployee.Id, ex.Message);
|
||||
await transaction.RollbackAsync();
|
||||
return ApiResponse<object>.ErrorResponse("An error occurred while converting payment request to expense.", ex.Message, 500);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInfo("End ChangeToExpanseFromPaymentRequestAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region =================================================================== Payment Request Functions ===================================================================
|
||||
|
||||
@ -24,6 +24,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
|
||||
Task<ApiResponse<object>> GetPaymentRequestFilterObjectAsync(Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> CreatePaymentRequestAsync(PaymentRequestDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> ChangePaymentRequestStatusAsync(PaymentRequestRecordDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
Task<ApiResponse<object>> ChangeToExpanseFromPaymentRequestAsync(ExpenseConversionDto model, Employee loggedInEmployee, Guid tenantId);
|
||||
#endregion
|
||||
#region =================================================================== Payment Request Functions ===================================================================
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user