diff --git a/Marco.Pms.Services/Controllers/ExpenseController.cs b/Marco.Pms.Services/Controllers/ExpenseController.cs index 878e1b9..582d3cf 100644 --- a/Marco.Pms.Services/Controllers/ExpenseController.cs +++ b/Marco.Pms.Services/Controllers/ExpenseController.cs @@ -242,6 +242,20 @@ namespace Marco.Pms.Services.Controllers var response = await _expensesService.GetRecurringPaymentDetailsAsync(id, recurringPaymentUId, loggedInEmployee, tenantId); return StatusCode(response.StatusCode, response); } + + [HttpPost("recurring-payment/create")] + public async Task CreateRecurringPayment([FromBody] CreateRecurringTemplateDto model) + { + var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync(); + var response = await _expensesService.CreateRecurringPaymentAsync(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 #region =================================================================== Payment Request Functions =================================================================== diff --git a/Marco.Pms.Services/Service/ExpensesService.cs b/Marco.Pms.Services/Service/ExpensesService.cs index 1f25bb6..537aa69 100644 --- a/Marco.Pms.Services/Service/ExpensesService.cs +++ b/Marco.Pms.Services/Service/ExpensesService.cs @@ -2785,6 +2785,118 @@ namespace Marco.Pms.Services.Service _logger.LogInfo("End GetRecurringPaymentDetailsAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id); } } + public async Task> CreateRecurringPaymentAsync(CreateRecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId) + { + _logger.LogInfo("Start CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}", loggedInEmployee.Id, tenantId); + + try + { + // Check if user has permission to create recurring payment templates + using var scope = _serviceScopeFactory.CreateScope(); + var permissionService = scope.ServiceProvider.GetRequiredService(); + var hasPermission = await permissionService.HasPermission(PermissionsMaster.ManageRecurring, loggedInEmployee.Id); + + if (!hasPermission) + { + _logger.LogWarning("Access denied: Employee {EmployeeId} attempted to create recurring payment template without required permission.", loggedInEmployee.Id); + return ApiResponse.ErrorResponse("You do not have access to create recurring template.", "Permission denied.", 403); + } + + // Concurrently fetch related entities required for validation and response mapping + 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 recurringStatusTask = Task.Run(async () => + { + await using var context = await _dbContextFactory.CreateDbContextAsync(); + return await context.RecurringPaymentStatus.FirstOrDefaultAsync(rs => rs.Id == model.StatusId); + }); + + 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, recurringStatusTask, currencyTask, projectTask); + + var expenseCategory = await expenseCategoryTask; + if (expenseCategory == null) + { + _logger.LogWarning("Expense Category not found with Id: {ExpenseCategoryId}", model.ExpenseCategoryId); + return ApiResponse.ErrorResponse("Expense Category not found.", "Expense Category not found.", 404); + } + + var recurringStatus = await recurringStatusTask; + if (recurringStatus == null) + { + _logger.LogWarning("Recurring Payment Status not found with Id: {StatusId}", model.StatusId); + return ApiResponse.ErrorResponse("Recurring Payment Status not found.", "Recurring Payment Status not found.", 404); + } + + var currency = await currencyTask; + if (currency == null) + { + _logger.LogWarning("Currency not found with Id: {CurrencyId}", model.CurrencyId); + return ApiResponse.ErrorResponse("Currency not found.", "Currency not found.", 404); + } + + var project = await projectTask; // Optional, can be null + + // Generate unique UID prefix and postfix per current month/year + string uIDPrefix = $"RP/{DateTime.Now:MMyy}"; + + var lastRecurringPayment = await _context.RecurringPayments + .Where(e => e.UIDPrefix == uIDPrefix) + .OrderByDescending(e => e.UIDPostfix) + .FirstOrDefaultAsync(); + + int uIDPostfix = lastRecurringPayment == null ? 1 : lastRecurringPayment.UIDPostfix + 1; + + // Map input DTO to entity and set additional fields + var recurringPayment = _mapper.Map(model); + recurringPayment.UIDPrefix = uIDPrefix; + recurringPayment.UIDPostfix = uIDPostfix; + recurringPayment.IsActive = true; + recurringPayment.CreatedAt = DateTime.UtcNow; + recurringPayment.CreatedById = loggedInEmployee.Id; + recurringPayment.TenantId = tenantId; + + _context.RecurringPayments.Add(recurringPayment); + await _context.SaveChangesAsync(); + + // Prepare response view model with enriched data + var response = _mapper.Map(recurringPayment); + response.RecurringPaymentUId = $"{recurringPayment.UIDPrefix}/{recurringPayment.UIDPostfix:D5}"; + response.CreatedBy = _mapper.Map(loggedInEmployee); + response.ExpenseCategory = _mapper.Map(expenseCategory); + response.Status = recurringStatus; + response.Currency = currency; + response.Project = _mapper.Map(project); + + _logger.LogInfo("Recurring Payment Template created successfully with UID: {RecurringPaymentUId} by EmployeeId: {EmployeeId}", response.RecurringPaymentUId, loggedInEmployee.Id); + + return ApiResponse.SuccessResponse(response, "Recurring Payment Template created successfully.", 201); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId}: {Message}", loggedInEmployee.Id, ex.Message); + return ApiResponse.ErrorResponse("An error occurred while creating the recurring payment template.", ex.Message, 500); + } + finally + { + _logger.LogInfo("End CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id); + } + } #endregion diff --git a/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs b/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs index 03352b0..63f8618 100644 --- a/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs +++ b/Marco.Pms.Services/Service/ServiceInterfaces/IExpensesService.cs @@ -32,6 +32,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces #region =================================================================== Recurring Payment Functions =================================================================== 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); #endregion