Added the get list recurring payment template API

This commit is contained in:
ashutosh.nehete 2025-11-04 18:45:57 +05:30
parent 177f44535a
commit 411e63db1b
4 changed files with 201 additions and 27 deletions

View File

@ -0,0 +1,14 @@
namespace Marco.Pms.Model.Filters
{
public class RecurringPaymentFilter
{
public List<Guid>? ProjectIds { get; set; }
public List<Guid>? StatusIds { get; set; }
public List<Guid>? CreatedByIds { get; set; }
public List<Guid>? CurrencyIds { get; set; }
public List<Guid>? ExpenseCategoryIds { get; set; }
public List<string>? Payees { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
}
}

View File

@ -215,10 +215,10 @@ namespace Marco.Pms.Services.Controllers
#region =================================================================== Recurring Payment Functions ===================================================================
[HttpGet("get/recurring-payment/list")]
public async Task<IActionResult> GetRecurringRequestList([FromQuery] string? searchString, [FromQuery] string? filter, [FromQuery] bool isActive = true, [FromQuery] int pageSize = 20, [FromQuery] int pageNumber = 1)
public async Task<IActionResult> GetRecurringPaymentList([FromQuery] string? searchString, [FromQuery] string? filter, [FromQuery] bool isActive = true, [FromQuery] int pageSize = 20, [FromQuery] int pageNumber = 1)
{
var loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
var response = await _expensesService.GetRecurringRequestListAsync(searchString, filter, isActive, pageSize, pageNumber, loggedInEmployee, tenantId);
var response = await _expensesService.GetRecurringPaymentListAsync(searchString, filter, isActive, pageSize, pageNumber, loggedInEmployee, tenantId);
return StatusCode(response.StatusCode, response);
}

View File

@ -2316,23 +2316,130 @@ namespace Marco.Pms.Services.Service
#region =================================================================== Recurring Payment Functions ===================================================================
public async Task<ApiResponse<object>> GetRecurringRequestListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId)
public async Task<ApiResponse<object>> GetRecurringPaymentListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start GetRecurringPaymentListAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}, PageNumber: {PageNumber}, PageSize: {PageSize}",
loggedInEmployee.Id, tenantId, pageNumber, pageSize);
try
{
// Check permissions concurrently: view self and view all recurring payments
var hasViewSelfPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ViewSelfRecurring, loggedInEmployee.Id);
});
var hasViewAllPermissionTask = Task.Run(async () =>
{
using var scope = _serviceScopeFactory.CreateScope();
var permissionService = scope.ServiceProvider.GetRequiredService<PermissionServices>();
return await permissionService.HasPermission(PermissionsMaster.ViewAllRecurring, loggedInEmployee.Id);
});
await Task.WhenAll(hasViewSelfPermissionTask, hasViewAllPermissionTask);
bool hasViewSelfPermission = hasViewSelfPermissionTask.Result;
bool hasViewAllPermission = hasViewAllPermissionTask.Result;
// Deny access if user has no relevant permissions
if (!hasViewAllPermission && !hasViewSelfPermission)
{
_logger.LogWarning("Access DENIED: Employee {EmployeeId} has no permission to view recurring payments.", loggedInEmployee.Id);
return ApiResponse<object>.SuccessResponse(new { }, "You do not have permission to view any recurring payment.", 200);
}
// Base query with required navigation properties and tenant + active status filters
var recurringPaymentQuery = _context.RecurringPayments
.Include(rp => rp.Currency)
.Include(rp => rp.ExpenseCategory)
.Include(rp => rp.Status)
.Include(rp => rp.Project)
.Include(rp => rp.CreatedBy).ThenInclude(e => e!.JobRole)
.Where(rp => rp.TenantId == tenantId && rp.IsActive == isActive);
var recurringPayments = await recurringPaymentQuery
.ToListAsync();
// If user has only view-self permission, restrict to their own payments
if (hasViewSelfPermission && !hasViewAllPermission)
{
recurringPaymentQuery = recurringPaymentQuery.Where(rp => rp.CreatedById == loggedInEmployee.Id);
}
// Deserialize and apply advanced filters
RecurringPaymentFilter? recurringPaymentFilter = TryDeserializeRecurringPaymentFilter(filter);
if (recurringPaymentFilter != null)
{
if (recurringPaymentFilter.ProjectIds?.Any() ?? false)
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => rp.ProjectId.HasValue && recurringPaymentFilter.ProjectIds.Contains(rp.ProjectId.Value));
}
if (recurringPaymentFilter.StatusIds?.Any() ?? false)
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => recurringPaymentFilter.StatusIds.Contains(rp.StatusId));
}
if (recurringPaymentFilter.CreatedByIds?.Any() ?? false)
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => recurringPaymentFilter.CreatedByIds.Contains(rp.CreatedById));
}
if (recurringPaymentFilter.CurrencyIds?.Any() ?? false)
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => recurringPaymentFilter.CurrencyIds.Contains(rp.CurrencyId));
}
if (recurringPaymentFilter.ExpenseCategoryIds?.Any() ?? false)
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => rp.ExpenseCategoryId.HasValue && recurringPaymentFilter.ExpenseCategoryIds.Contains(rp.ExpenseCategoryId.Value));
}
if (recurringPaymentFilter.Payees?.Any() ?? false)
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => recurringPaymentFilter.Payees.Contains(rp.Payee));
}
if (recurringPaymentFilter.StartDate.HasValue && recurringPaymentFilter.EndDate.HasValue)
{
DateTime startDate = recurringPaymentFilter.StartDate.Value.Date;
DateTime endDate = recurringPaymentFilter.EndDate.Value.Date;
recurringPaymentQuery = recurringPaymentQuery
.Where(rp => rp.CreatedAt.Date >= startDate && rp.CreatedAt.Date <= endDate);
}
}
// Apply search string filter if provided
if (!string.IsNullOrWhiteSpace(searchString))
{
recurringPaymentQuery = recurringPaymentQuery
.Where(rp =>
rp.Payee.Contains(searchString) ||
rp.Title.Contains(searchString) ||
(rp.UIDPrefix + "/" + rp.UIDPostfix.ToString().PadLeft(5, '0')).Contains(searchString)
);
}
// Get total count of matching records for pagination
var totalEntities = await recurringPaymentQuery.CountAsync();
// Calculate total pages (ceil)
var totalPages = (int)Math.Ceiling((double)totalEntities / pageSize);
// Fetch paginated data ordered by creation date desc
var recurringPayments = await recurringPaymentQuery
.OrderByDescending(rp => rp.CreatedAt)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// Map entities to view models and set recurring payment UID
var results = recurringPayments.Select(rp =>
{
var result = _mapper.Map<RecurringPaymentVM>(rp);
result.RecurringPaymentUId = $"{rp.UIDPrefix}/{rp.UIDPostfix:D5}";
return result;
var vm = _mapper.Map<RecurringPaymentVM>(rp);
vm.RecurringPaymentUId = $"{rp.UIDPrefix}/{rp.UIDPostfix:D5}";
return vm;
}).ToList();
var response = new
@ -2342,8 +2449,23 @@ namespace Marco.Pms.Services.Service
TotalEntities = totalEntities,
Data = results,
};
return ApiResponse<object>.SuccessResponse(response, $"{results.Count} Recurring payments fetched successfully", 200);
_logger.LogInfo("Recurring payments fetched successfully: {Count} records for TenantId: {TenantId}, Page: {PageNumber}/{TotalPages}",
results.Count, tenantId, pageNumber, totalPages);
return ApiResponse<object>.SuccessResponse(response, $"{results.Count} recurring payments fetched successfully.", 200);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in GetRecurringPaymentListAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}: {Message}", loggedInEmployee.Id, tenantId, ex.Message);
return ApiResponse<object>.ErrorResponse("An error occurred while fetching recurring payments.", ex.Message, 500);
}
finally
{
_logger.LogInfo("End GetRecurringPaymentListAsync called by EmployeeId: {EmployeeId}", loggedInEmployee.Id);
}
}
public async Task<ApiResponse<object>> CreateRecurringPaymentAsync(RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId)
{
_logger.LogInfo("Start CreateRecurringPaymentAsync called by EmployeeId: {EmployeeId} for TenantId: {TenantId}", loggedInEmployee.Id, tenantId);
@ -2856,7 +2978,7 @@ namespace Marco.Pms.Services.Service
}
catch (JsonException ex)
{
_logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeFilter), filter);
_logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializePaymentRequestFilter), filter);
// If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
try
@ -2871,7 +2993,45 @@ namespace Marco.Pms.Services.Service
catch (JsonException ex1)
{
// If both attempts fail, log the final error and return null.
_logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeFilter), filter);
_logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializePaymentRequestFilter), filter);
return null;
}
}
return expenseFilter;
}
private RecurringPaymentFilter? TryDeserializeRecurringPaymentFilter(string? filter)
{
if (string.IsNullOrWhiteSpace(filter))
{
return null;
}
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
RecurringPaymentFilter? expenseFilter = null;
try
{
// First, try to deserialize directly. This is the expected case (e.g., from a web client).
expenseFilter = JsonSerializer.Deserialize<RecurringPaymentFilter>(filter, options);
}
catch (JsonException ex)
{
_logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeRecurringPaymentFilter), filter);
// If direct deserialization fails, it might be an escaped string (common with tools like Postman or some mobile clients).
try
{
// Unescape the string first, then deserialize the result.
string unescapedJsonString = JsonSerializer.Deserialize<string>(filter, options) ?? "";
if (!string.IsNullOrWhiteSpace(unescapedJsonString))
{
expenseFilter = JsonSerializer.Deserialize<RecurringPaymentFilter>(unescapedJsonString, options);
}
}
catch (JsonException ex1)
{
// If both attempts fail, log the final error and return null.
_logger.LogError(ex1, "[{MethodName}] All attempts to deserialize the filter failed. Filter will be ignored. Filter: {Filter}", nameof(TryDeserializeRecurringPaymentFilter), filter);
return null;
}
}

View File

@ -29,7 +29,7 @@ namespace Marco.Pms.Services.Service.ServiceInterfaces
#endregion
#region =================================================================== Recurring Payment Functions ===================================================================
Task<ApiResponse<object>> GetRecurringRequestListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> GetRecurringPaymentListAsync(string? searchString, string? filter, bool isActive, int pageSize, int pageNumber, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> CreateRecurringPaymentAsync(RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId);
Task<ApiResponse<object>> EditRecurringPaymentAsync(Guid id, RecurringTemplateDto model, Employee loggedInEmployee, Guid tenantId);
#endregion