Added the dynamic filter in collection and expense controller
This commit is contained in:
parent
e73413c849
commit
bd4bd90e7b
@ -3,6 +3,8 @@
|
|||||||
public class AdvanceFilter
|
public class AdvanceFilter
|
||||||
{
|
{
|
||||||
// The dynamic filters from your JSON
|
// The dynamic filters from your JSON
|
||||||
|
public DateDynamicFilter? DateFilter { get; set; }
|
||||||
|
public List<ListDynamicFilter>? Filters { get; set; }
|
||||||
public List<SortItem>? SortFilters { get; set; }
|
public List<SortItem>? SortFilters { get; set; }
|
||||||
public List<SearchItem>? SearchFilters { get; set; }
|
public List<SearchItem>? SearchFilters { get; set; }
|
||||||
public List<AdvanceItem>? AdvanceFilters { get; set; }
|
public List<AdvanceItem>? AdvanceFilters { get; set; }
|
||||||
|
|||||||
9
Marco.Pms.Model/Filters/DateDynamicFilter.cs
Normal file
9
Marco.Pms.Model/Filters/DateDynamicFilter.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Marco.Pms.Model.Filters
|
||||||
|
{
|
||||||
|
public class DateDynamicFilter
|
||||||
|
{
|
||||||
|
public string Column { get; set; } = string.Empty;
|
||||||
|
public DateTime StartValue { get; set; }
|
||||||
|
public DateTime EndValue { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Marco.Pms.Model/Filters/ListDynamicFilter.cs
Normal file
8
Marco.Pms.Model/Filters/ListDynamicFilter.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Marco.Pms.Model.Filters
|
||||||
|
{
|
||||||
|
public class ListDynamicFilter
|
||||||
|
{
|
||||||
|
public string Column { get; set; } = string.Empty;
|
||||||
|
public List<Guid> Values { get; set; } = new List<Guid>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -168,12 +168,23 @@ namespace Marco.Pms.Services.Controllers
|
|||||||
|
|
||||||
query = query.ApplyCustomFilters(advanceFilter, "InvoiceDate");
|
query = query.ApplyCustomFilters(advanceFilter, "InvoiceDate");
|
||||||
|
|
||||||
if (advanceFilter?.SearchFilters != null)
|
if (advanceFilter != null)
|
||||||
{
|
{
|
||||||
var invoiceSearchFilter = advanceFilter.SearchFilters.Where(f => f.Column != "ProjectName").ToList();
|
if (advanceFilter.Filters != null)
|
||||||
if (invoiceSearchFilter.Any())
|
|
||||||
{
|
{
|
||||||
query = query.ApplySearchFilters(invoiceSearchFilter);
|
query = query.ApplyListFilters(advanceFilter.Filters);
|
||||||
|
}
|
||||||
|
if (advanceFilter.DateFilter != null)
|
||||||
|
{
|
||||||
|
query = query.ApplyDateFilter(advanceFilter.DateFilter);
|
||||||
|
}
|
||||||
|
if (advanceFilter.SearchFilters != null)
|
||||||
|
{
|
||||||
|
var invoiceSearchFilter = advanceFilter.SearchFilters.Where(f => f.Column != "ProjectName").ToList();
|
||||||
|
if (invoiceSearchFilter.Any())
|
||||||
|
{
|
||||||
|
query = query.ApplySearchFilters(invoiceSearchFilter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Marco.Pms.Model.Filters;
|
using Marco.Pms.Model.Filters;
|
||||||
|
using System.Data;
|
||||||
using System.Linq.Dynamic.Core;
|
using System.Linq.Dynamic.Core;
|
||||||
|
|
||||||
namespace Marco.Pms.Services.Extensions
|
namespace Marco.Pms.Services.Extensions
|
||||||
@ -69,9 +70,16 @@ namespace Marco.Pms.Services.Extensions
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies search filters to the given IQueryable.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||||
|
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||||
|
/// <param name="searchFilters">The list of search filters to apply.</param>
|
||||||
|
/// <returns>The filtered IQueryable.</returns>
|
||||||
public static IQueryable<T> ApplySearchFilters<T>(this IQueryable<T> query, List<SearchItem> searchFilters)
|
public static IQueryable<T> ApplySearchFilters<T>(this IQueryable<T> query, List<SearchItem> searchFilters)
|
||||||
{
|
{
|
||||||
// 1. Apply Search Filters (Contains/Text search)
|
// Apply search filters to the query
|
||||||
if (searchFilters.Any())
|
if (searchFilters.Any())
|
||||||
{
|
{
|
||||||
foreach (var search in searchFilters)
|
foreach (var search in searchFilters)
|
||||||
@ -86,10 +94,70 @@ namespace Marco.Pms.Services.Extensions
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies group by filters to the given IQueryable.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||||
|
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||||
|
/// <param name="groupByColumn">The column to group by.</param>
|
||||||
|
/// <returns>The grouped IQueryable.</returns>
|
||||||
public static IQueryable<T> ApplyGroupByFilters<T>(this IQueryable<T> query, string groupByColumn)
|
public static IQueryable<T> ApplyGroupByFilters<T>(this IQueryable<T> query, string groupByColumn)
|
||||||
{
|
{
|
||||||
|
// Group the query by the specified column and reshape the result to { Key: "Value", Items: [...] }
|
||||||
query.GroupBy(groupByColumn, "it")
|
query.GroupBy(groupByColumn, "it")
|
||||||
.Select("new (Key, it as Items)"); // Reshape to { Key: "Value", Items: [...] }
|
.Select("new (Key, it as Items)");
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies list filters to the given IQueryable.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||||
|
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||||
|
/// <param name="filters">The list of filters to apply.</param>
|
||||||
|
/// <returns>The filtered IQueryable.</returns>
|
||||||
|
public static IQueryable<T> ApplyListFilters<T>(this IQueryable<T> query, List<ListDynamicFilter> filters)
|
||||||
|
{
|
||||||
|
// Check if there are any filters
|
||||||
|
if (filters == null || !filters.Any()) return query;
|
||||||
|
|
||||||
|
// Apply filters to the query
|
||||||
|
foreach (var filter in filters)
|
||||||
|
{
|
||||||
|
// Skip if column is empty or values are null or empty
|
||||||
|
if (string.IsNullOrWhiteSpace(filter.Column) || filter.Values == null || !filter.Values.Any()) continue;
|
||||||
|
|
||||||
|
// Apply filter to the query
|
||||||
|
query = query.Where($"@0.Contains({filter.Column})", filter.Values);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the filtered query
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Applies date filters to the given IQueryable.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the elements in the IQueryable.</typeparam>
|
||||||
|
/// <param name="query">The IQueryable to apply the filters to.</param>
|
||||||
|
/// <param name="dateFilter">The date filter to apply.</param>
|
||||||
|
/// <returns>The filtered IQueryable.</returns>
|
||||||
|
public static IQueryable<T> ApplyDateFilter<T>(this IQueryable<T> query, DateDynamicFilter dateFilter)
|
||||||
|
{
|
||||||
|
// Check if date filter is null or column is empty
|
||||||
|
if (dateFilter == null || string.IsNullOrWhiteSpace(dateFilter.Column)) return query;
|
||||||
|
|
||||||
|
// Convert start and end values to date
|
||||||
|
var startValue = dateFilter.StartValue.Date;
|
||||||
|
var endValue = dateFilter.EndValue.Date.AddDays(1);
|
||||||
|
|
||||||
|
// Apply a filter to include items with a date greater than or equal to the start value
|
||||||
|
query = query.Where($"{dateFilter.Column} >= @0", startValue);
|
||||||
|
|
||||||
|
// Apply a filter to include items with a date less than or equal to the end value
|
||||||
|
query = query.Where($"{dateFilter.Column} < @0", endValue);
|
||||||
|
|
||||||
|
// Return the filtered IQueryable
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ using Marco.Pms.Model.ViewModels.Expenses;
|
|||||||
using Marco.Pms.Model.ViewModels.Expenses.Masters;
|
using Marco.Pms.Model.ViewModels.Expenses.Masters;
|
||||||
using Marco.Pms.Model.ViewModels.Master;
|
using Marco.Pms.Model.ViewModels.Master;
|
||||||
using Marco.Pms.Model.ViewModels.Projects;
|
using Marco.Pms.Model.ViewModels.Projects;
|
||||||
|
using Marco.Pms.Services.Extensions;
|
||||||
using Marco.Pms.Services.Helpers;
|
using Marco.Pms.Services.Helpers;
|
||||||
using Marco.Pms.Services.Service.ServiceInterfaces;
|
using Marco.Pms.Services.Service.ServiceInterfaces;
|
||||||
using MarcoBMS.Services.Service;
|
using MarcoBMS.Services.Service;
|
||||||
@ -128,7 +129,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
|
|
||||||
// 2. --- Deserialize Filter and Apply ---
|
// 2. --- Deserialize Filter and Apply ---
|
||||||
ExpensesFilter? expenseFilter = TryDeserializeFilter(filter);
|
AdvanceFilter? advanceFilter = TryDeserializeAdvanceFilter(filter);
|
||||||
|
|
||||||
//var (totalPages, totalCount, cacheList) = await _cache.GetExpenseListAsync(tenantId, loggedInEmployeeId, hasViewAllPermissionTask.Result, hasViewSelfPermissionTask.Result,
|
//var (totalPages, totalCount, cacheList) = await _cache.GetExpenseListAsync(tenantId, loggedInEmployeeId, hasViewAllPermissionTask.Result, hasViewSelfPermissionTask.Result,
|
||||||
// pageNumber, pageSize, expenseFilter, searchString);
|
// pageNumber, pageSize, expenseFilter, searchString);
|
||||||
@ -168,45 +169,28 @@ namespace Marco.Pms.Services.Service
|
|||||||
_logger.LogInfo("User {EmployeeId} has 'View Self' permission. Restricting query to their expenses.", loggedInEmployeeId);
|
_logger.LogInfo("User {EmployeeId} has 'View Self' permission. Restricting query to their expenses.", loggedInEmployeeId);
|
||||||
expensesQuery = expensesQuery.Where(e => e.CreatedById == loggedInEmployeeId);
|
expensesQuery = expensesQuery.Where(e => e.CreatedById == loggedInEmployeeId);
|
||||||
}
|
}
|
||||||
|
expensesQuery = expensesQuery.ApplyCustomFilters(advanceFilter, "CreatedAt");
|
||||||
if (expenseFilter != null)
|
if (advanceFilter != null)
|
||||||
{
|
{
|
||||||
if (expenseFilter.StartDate.HasValue && expenseFilter.EndDate.HasValue)
|
if (advanceFilter.Filters != null)
|
||||||
{
|
{
|
||||||
if (expenseFilter.IsTransactionDate)
|
expensesQuery = expensesQuery.ApplyListFilters(advanceFilter.Filters);
|
||||||
|
}
|
||||||
|
if (advanceFilter.DateFilter != null)
|
||||||
|
{
|
||||||
|
expensesQuery = expensesQuery.ApplyDateFilter(advanceFilter.DateFilter);
|
||||||
|
}
|
||||||
|
if (advanceFilter.SearchFilters != null)
|
||||||
|
{
|
||||||
|
var invoiceSearchFilter = advanceFilter.SearchFilters.Where(f => f.Column != "ProjectName").ToList();
|
||||||
|
if (invoiceSearchFilter.Any())
|
||||||
{
|
{
|
||||||
expensesQuery = expensesQuery.Where(e => e.TransactionDate.Date >= expenseFilter.StartDate.Value.Date && e.TransactionDate.Date <= expenseFilter.EndDate.Value.Date);
|
expensesQuery = expensesQuery.ApplySearchFilters(invoiceSearchFilter);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
expensesQuery = expensesQuery.Where(e => e.CreatedAt.Date >= expenseFilter.StartDate.Value.Date && e.CreatedAt.Date <= expenseFilter.EndDate.Value.Date);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(advanceFilter.GroupByColumn))
|
||||||
if (expenseFilter.ProjectIds?.Any() == true)
|
|
||||||
{
|
{
|
||||||
expensesQuery = expensesQuery.Where(e => expenseFilter.ProjectIds.Contains(e.ProjectId));
|
expensesQuery = expensesQuery.ApplyGroupByFilters(advanceFilter.GroupByColumn);
|
||||||
}
|
|
||||||
|
|
||||||
if (expenseFilter.StatusIds?.Any() == true)
|
|
||||||
{
|
|
||||||
expensesQuery = expensesQuery.Where(e => expenseFilter.StatusIds.Contains(e.StatusId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expenseFilter.PaidById?.Any() == true)
|
|
||||||
{
|
|
||||||
expensesQuery = expensesQuery.Where(e => expenseFilter.PaidById.Contains(e.PaidById));
|
|
||||||
}
|
|
||||||
if (expenseFilter.ExpenseCategoryIds?.Any() == true)
|
|
||||||
{
|
|
||||||
expensesQuery = expensesQuery.Where(e => expenseFilter.ExpenseCategoryIds.Contains(e.ExpenseCategoryId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only allow filtering by 'CreatedBy' if the user has 'View All' permission.
|
|
||||||
if (expenseFilter.CreatedByIds?.Any() == true && hasViewAllPermissionTask.Result)
|
|
||||||
{
|
|
||||||
expensesQuery = expensesQuery.Where(e => expenseFilter.CreatedByIds.Contains(e.CreatedById));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +212,7 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
// 5. --- Execute Query and Map Results ---
|
// 5. --- Execute Query and Map Results ---
|
||||||
var expensesList = await expensesQuery
|
var expensesList = await expensesQuery
|
||||||
.OrderByDescending(e => e.CreatedAt)
|
//.OrderByDescending(e => e.CreatedAt)
|
||||||
.Skip((pageNumber - 1) * pageSize)
|
.Skip((pageNumber - 1) * pageSize)
|
||||||
.Take(pageSize).ToListAsync();
|
.Take(pageSize).ToListAsync();
|
||||||
|
|
||||||
@ -302,7 +286,6 @@ namespace Marco.Pms.Services.Service
|
|||||||
|
|
||||||
var response = new
|
var response = new
|
||||||
{
|
{
|
||||||
CurrentFilter = expenseFilter,
|
|
||||||
CurrentPage = pageNumber,
|
CurrentPage = pageNumber,
|
||||||
TotalPages = totalPages,
|
TotalPages = totalPages,
|
||||||
TotalEntites = totalEntites,
|
TotalEntites = totalEntites,
|
||||||
@ -3746,6 +3729,45 @@ namespace Marco.Pms.Services.Service
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region =================================================================== Helper Functions ===================================================================
|
#region =================================================================== Helper Functions ===================================================================
|
||||||
|
|
||||||
|
private AdvanceFilter? TryDeserializeAdvanceFilter(string? filter)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(filter))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
AdvanceFilter? advanceFilter = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// First, try to deserialize directly. This is the expected case (e.g., from a web client).
|
||||||
|
advanceFilter = JsonSerializer.Deserialize<AdvanceFilter>(filter, options);
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "[{MethodName}] Failed to directly deserialize filter. Attempting to unescape and re-parse. Filter: {Filter}", nameof(TryDeserializeFilter), 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))
|
||||||
|
{
|
||||||
|
advanceFilter = JsonSerializer.Deserialize<AdvanceFilter>(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(TryDeserializeFilter), filter);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return advanceFilter;
|
||||||
|
}
|
||||||
private async Task<bool> HasPermissionAsync(Guid permission, Guid employeeId)
|
private async Task<bool> HasPermissionAsync(Guid permission, Guid employeeId)
|
||||||
{
|
{
|
||||||
using var scope = _serviceScopeFactory.CreateScope();
|
using var scope = _serviceScopeFactory.CreateScope();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user