From c71343d5504c994cb46c330274309910fc455713 Mon Sep 17 00:00:00 2001 From: "ashutosh.nehete" Date: Fri, 7 Nov 2025 17:58:15 +0530 Subject: [PATCH] Added the API to create payment request from recurring payment --- .../Controllers/ExpenseController.cs | 9 ++ Marco.Pms.Services/Service/ExpensesService.cs | 127 ++++++++++++++++++ .../ServiceInterfaces/IExpensesService.cs | 1 + 3 files changed, 137 insertions(+) diff --git a/Marco.Pms.Services/Controllers/ExpenseController.cs b/Marco.Pms.Services/Controllers/ExpenseController.cs index 5e324f0..458ac14 100644 --- a/Marco.Pms.Services/Controllers/ExpenseController.cs +++ b/Marco.Pms.Services/Controllers/ExpenseController.cs @@ -257,6 +257,15 @@ namespace Marco.Pms.Services.Controllers } + [HttpPost("recurring-payment/convert/payment-request")] + public async Task PaymentRequestConversion(PaymentRequestConversionDto model) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _expensesService.PaymentRequestConversionAsync(model.RecurringTemplateIds, loggedInEmployee, tenantId); + + return StatusCode(response.StatusCode, response); + } + [HttpPut("recurring-payment/edit/{id}")] public async Task EditRecurringPaymentAsync(Guid id, [FromBody] UpdateRecurringTemplateDto model) { diff --git a/Marco.Pms.Services/Service/ExpensesService.cs b/Marco.Pms.Services/Service/ExpensesService.cs index f61788a..90e5e0f 100644 --- a/Marco.Pms.Services/Service/ExpensesService.cs +++ b/Marco.Pms.Services/Service/ExpensesService.cs @@ -2897,6 +2897,133 @@ namespace Marco.Pms.Services.Service _logger.LogInfo("End CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id); } } + public async Task> PaymentRequestConversionAsync(List recurringTemplateIds, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("Start PaymentRequestConversionAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId} with RecurringTemplateIds: {RecurringTemplateIds}", + loggedInEmployee.Id, tenantId, recurringTemplateIds); + + // SuperAdmin check - restrict access only to specific user + var superAdminId = Guid.Parse("08dd8b35-d98b-44f1-896d-12aec3f035aa"); + if (loggedInEmployee.Id != superAdminId) + { + _logger.LogWarning("Access denied for EmployeeId: {EmployeeId}. Only super admin can perform this operation.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("Access Denied", "User does not have permission to access this function", 403); + } + + // Get active recurring payments matching the provided IDs and tenant + var recurringPayments = await _context.RecurringPayments + .AsNoTracking() + .Where(rp => recurringTemplateIds.Contains(rp.Id) + && rp.IsActive + && rp.StatusId == ActiveTemplateStatus + && rp.TenantId == tenantId) + .ToListAsync(); + + if (!recurringPayments.Any()) + { + _logger.LogWarning("No active recurring payments found for TenantId: {TenantId} and given IDs.", tenantId); + return ApiResponse.ErrorResponse("Recurring template not found", "Recurring template not found", 404); + } + + var updatedRecurringPayments = new List(); + var paymentRequests = new List(); + + // Generate UID prefix for payment requests for this period (month/year) + string uIDPrefix = $"PR/{DateTime.Now:MM.yy}"; + + // Get last generated payment request for UID postfixing (to maintain unique numbering) + var lastPR = await _context.PaymentRequests + .Where(pr => pr.UIDPrefix == uIDPrefix) + .OrderByDescending(pr => pr.UIDPostfix) + .FirstOrDefaultAsync(); + + int uIDPostfix = lastPR == null ? 1 : lastPR.UIDPostfix + 1; + + foreach (var recurringPayment in recurringPayments) + { + // Check if recurring payment is applicable for generating a new payment request + var isApplicable = IsRecurringApplicable( + recurringPayment.NumberOfIteration, + recurringPayment.Frequency, + recurringPayment.StrikeDate.Date, + recurringPayment.LatestPRGeneratedAt); + + if (isApplicable) + { + // Update latest generated date to today (UTC) + recurringPayment.LatestPRGeneratedAt = DateTime.UtcNow.Date; + updatedRecurringPayments.Add(recurringPayment); + + // Create new payment request mapped from recurring payment data + var newPaymentRequest = new PaymentRequest + { + Id = Guid.NewGuid(), + Title = recurringPayment.Title, + Description = recurringPayment.Description, + UIDPrefix = uIDPrefix, + UIDPostfix = uIDPostfix, + Payee = recurringPayment.Payee, + IsAdvancePayment = false, + CurrencyId = recurringPayment.CurrencyId, + Amount = recurringPayment.Amount, + DueDate = DateTime.UtcNow.AddDays(recurringPayment.PaymentBufferDays), + ProjectId = recurringPayment.ProjectId, + RecurringPaymentId = recurringPayment.Id, + ExpenseCategoryId = recurringPayment.ExpenseCategoryId, + ExpenseStatusId = Review, + IsExpenseCreated = false, + CreatedAt = DateTime.UtcNow, + CreatedById = loggedInEmployee.Id, + IsActive = true, + TenantId = recurringPayment.TenantId + }; + paymentRequests.Add(newPaymentRequest); + + uIDPostfix++; + } + } + + if (!updatedRecurringPayments.Any()) + { + _logger.LogWarning("No applicable recurring payments for conversion found for TenantId: {TenantId}.", tenantId); + return ApiResponse.ErrorResponse("No applicable recurring templates found to convert", "No applicable recurring templates found", 404); + } + + try + { + // Update recurring payments with latest generated dates + _context.RecurringPayments.UpdateRange(updatedRecurringPayments); + + // Add newly created payment requests + if (paymentRequests.Any()) + { + _context.PaymentRequests.AddRange(paymentRequests); + } + + await _context.SaveChangesAsync(); + + _logger.LogInfo("{Count} payment requests created successfully from recurring payments by EmployeeId: {EmployeeId} for TenantId: {TenantId}", + paymentRequests.Count, loggedInEmployee.Id, tenantId); + + return ApiResponse.SuccessResponse(recurringTemplateIds, $"{paymentRequests.Count} conversion(s) to payment request completed successfully.", 201); + } + catch (DbUpdateException ex) + { + _logger.LogError(ex, "Database error during PaymentRequestConversionAsync for TenantId: {TenantId}, EmployeeId: {EmployeeId}: {Message}. Inner exception: {InnerException}", + tenantId, loggedInEmployee.Id, ex.Message, ex.InnerException?.Message ?? ""); + return ApiResponse.ErrorResponse("Database error occurred", ExceptionMapper(ex), 500); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unexpected error during PaymentRequestConversionAsync for TenantId: {TenantId}, EmployeeId: {EmployeeId}: {Message}", + tenantId, loggedInEmployee.Id, ex.Message); + return ApiResponse.ErrorResponse("An unexpected error occurred", ex.Message, 500); + } + finally + { + _logger.LogInfo("End PaymentRequestConversionAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}", loggedInEmployee.Id, tenantId); + } + } public async Task> EditRecurringPaymentAsync(Guid id, UpdateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId) { _logger.LogInfo("Start EditRecurringPaymentAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}, RecurringPaymentId: {RecurringPaymentId}", diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs index b323d02..1af25c4 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs @@ -33,6 +33,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces Task> GetRecurringPaymentListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId); Task> GetRecurringPaymentDetailsAsync(Guid? id, string? recurringPaymentUId, Employee loggedInEmployee, Guid tenantId); Task> CreateRecurringPaymentAsync(CreateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId); + Task> PaymentRequestConversionAsync(List RecurringTemplateIds, Employee loggedInEmployee, Guid tenantId); Task> EditRecurringPaymentAsync(Guid id, UpdateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId); #endregion