|  | @@ -64,6 +64,8 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // !!! If updating this list, make sure to update everywhere this list is used! That includes SQLiteProvider, the ToString() functions,
 | 
	
		
			
				|  |  | +    // the FilterExtensions.MatchOperator, but possible otherwhere to; make sure everything is updated.
 | 
	
		
			
				|  |  |      public enum Operator
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          IsEqualTo,
 | 
	
	
		
			
				|  | @@ -84,13 +86,15 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |          Not = byte.MaxValue
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      public enum ListOperator
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          Includes,
 | 
	
		
			
				|  |  |          Excludes
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    // !!! If updating this list, make sure to update everywhere this list is
 | 
	
		
			
				|  |  | +    // used! That includes SQLiteProvider the FilterConstants.Resolve,
 | 
	
		
			
				|  |  | +    // but possible otherwhere to; make sure everything is updated.
 | 
	
		
			
				|  |  |      public enum FilterConstant
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          Today,
 | 
	
	
		
			
				|  | @@ -201,6 +205,52 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              return new FilterConstant[] { };
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        public static object? Resolve(this FilterConstant constant, Type type)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            return constant switch
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                FilterConstant.Null => CoreUtils.GetDefault(type),
 | 
	
		
			
				|  |  | +                FilterConstant.Today => DateTime.Today,
 | 
	
		
			
				|  |  | +                FilterConstant.Now => DateTime.Now,
 | 
	
		
			
				|  |  | +                FilterConstant.Zero => CoreUtils.GetDefault(type),
 | 
	
		
			
				|  |  | +                FilterConstant.OneWeekAgo => DateTime.Today.AddDays(-7),
 | 
	
		
			
				|  |  | +                FilterConstant.OneWeekAhead => DateTime.Today.AddDays(7),
 | 
	
		
			
				|  |  | +                FilterConstant.OneMonthAgo => DateTime.Today.AddMonths(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.OneMonthAhead => DateTime.Today.AddMonths(1),
 | 
	
		
			
				|  |  | +                FilterConstant.ThreeMonthsAgo => DateTime.Today.AddMonths(-3),
 | 
	
		
			
				|  |  | +                FilterConstant.ThreeMonthsAhead => DateTime.Today.AddMonths(3),
 | 
	
		
			
				|  |  | +                FilterConstant.SixMonthsAgo => DateTime.Today.AddMonths(-6),
 | 
	
		
			
				|  |  | +                FilterConstant.SixMonthsAhead => DateTime.Today.AddMonths(6),
 | 
	
		
			
				|  |  | +                FilterConstant.OneYearAgo => DateTime.Today.AddYears(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.OneYearAhead => DateTime.Today.AddYears(1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfLastWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(-7),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfLastWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfThisWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfThisWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(7).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfNextWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(7),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfNextWeek => DateTime.Today.StartOfWeek(DayOfWeek.Monday).AddDays(14).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfLastMonth => DateTime.Today.StartOfMonth().AddMonths(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfLastMonth => DateTime.Today.StartOfMonth().AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfThisMonth => DateTime.Today.StartOfMonth(),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfThisMonth => DateTime.Today.StartOfMonth().AddMonths(1).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfNextMonth => DateTime.Today.StartOfMonth().AddMonths(1),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfNextMonth => DateTime.Today.StartOfMonth().AddMonths(2).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfLastCalendarYear => DateTime.Today.StartOfYear().AddYears(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfLastCalendarYear => DateTime.Today.StartOfYear().AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfThisCalendarYear => DateTime.Today.StartOfYear(),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfThisCalendarYear => DateTime.Today.StartOfYear().AddYears(1).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfNextCalendarYear => DateTime.Today.StartOfYear().AddYears(1),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfNextCalendarYear => DateTime.Today.StartOfYear().AddYears(2).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfLastFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(-18),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfLastFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(-6).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfThisFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(-6),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfThisFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(6).AddTicks(-1),
 | 
	
		
			
				|  |  | +                FilterConstant.StartOfNextFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(6),
 | 
	
		
			
				|  |  | +                FilterConstant.EndOfNextFinancialYear => DateTime.Today.AddMonths(6).StartOfYear().AddMonths(18).AddTicks(-1),
 | 
	
		
			
				|  |  | +                _ => throw new Exception($"Cannot handle {constant}!"),
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public class CustomFilterValue
 | 
	
	
		
			
				|  | @@ -228,7 +278,13 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          bool IsNot { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// List of filters to apply to the main filter with boolean operator AND. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  |          IEnumerable<IFilter> Ands { get; }
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// List of filters to apply to the main filter with boolean operator OR. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  |          IEnumerable<IFilter> Ors { get; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public IFilter And(IFilter filter);
 | 
	
	
		
			
				|  | @@ -378,11 +434,6 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |          public static Filter<T> None<T>() => new Filter<T>().None();
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    public interface IFilter2<T>
 | 
	
		
			
				|  |  | -    {
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      public class Filter<T> : IFilter
 | 
	
		
			
				|  |  |      {
 | 
	
		
			
				|  |  |          public Type Type => PropertyDefinition?.PropertyType ?? typeof(object);
 | 
	
	
		
			
				|  | @@ -425,8 +476,14 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  |          public Filter<T>? Parent { get; set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// List of filters to apply to the main filter with boolean operator AND. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  |          public List<Filter<T>> Ands { get; private set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// List of filters to apply to the main filter with boolean operator OR. The <see cref="Ands"/> take precedence over the <see cref="Ors"/>.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  |          public List<Filter<T>> Ors { get; private set; }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          public Operator Operator { get; set; }
 | 
	
	
		
			
				|  | @@ -1901,6 +1958,129 @@ namespace InABox.Core
 | 
	
		
			
				|  |  |                  return filter1.And(filter2);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private static object? Encode(Type T, object? value)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if(T == typeof(string) && Equals(value, null))
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return "";
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            return value;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private static bool MatchOperator<T>(this Filter<T> filter, T obj, IQueryProviderFactory query)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            if(filter.Operator == Operator.All)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else if(filter.Operator == Operator.None)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (filter.PropertyDefinition is null) return false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if(filter.Value is CustomFilterValue)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                throw new Exception("Custom Filter Value not able to be processed!");
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            var valueType = filter.PropertyDefinition.PropertyType;
 | 
	
		
			
				|  |  | +            var objValue = Encode(valueType, filter.PropertyDefinition.Getter()(obj));
 | 
	
		
			
				|  |  | +            if(filter.Operator == Operator.InList || filter.Operator == Operator.NotInList)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if(filter.Value is Array arr && arr.Length == 0)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    return filter.Operator != Operator.InList;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else if(filter.Value is IEnumerable enumerable)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    foreach(var item in enumerable)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        if (Equals(Encode(valueType, item), objValue))
 | 
	
		
			
				|  |  | +                            return filter.Operator == Operator.InList;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    return filter.Operator == Operator.NotInList;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    return false;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else if(filter.Operator == Operator.InQuery || filter.Operator == Operator.NotInQuery)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                if(filter.Value is ISubQuery subQuery)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    var result = query.Create(subQuery.GetQueryType());
 | 
	
		
			
				|  |  | +                    var subFilter = Filter.Create(subQuery.GetQueryType(), subQuery.GetColumn().Name, Operator.IsEqualTo, objValue);
 | 
	
		
			
				|  |  | +                    if(subQuery.GetFilter() is IFilter subQueryFilter)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        subFilter.And(subQueryFilter);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    var inQuery = result.Query(subFilter, Columns.None(subQuery.GetQueryType()))
 | 
	
		
			
				|  |  | +                        .Rows.Count > 0;
 | 
	
		
			
				|  |  | +                    return inQuery ? filter.Operator == Operator.InQuery : filter.Operator == Operator.NotInQuery;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    return false;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            else
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                object? value;
 | 
	
		
			
				|  |  | +                if(filter.Value is FilterConstant constant)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    value = constant.Resolve(filter.PropertyDefinition.PropertyType);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    value = filter.Value;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                value = Encode(filter.PropertyDefinition.PropertyType, value);
 | 
	
		
			
				|  |  | +                return filter.Operator switch
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    Operator.IsEqualTo => Equals(objValue, value),
 | 
	
		
			
				|  |  | +                    Operator.IsNotEqualTo => !Equals(objValue, value),
 | 
	
		
			
				|  |  | +                    Operator.IsGreaterThan => TryCompare(objValue, value, out var comparison) && comparison > 0,
 | 
	
		
			
				|  |  | +                    Operator.IsGreaterThanOrEqualTo => TryCompare(objValue, value, out var comparison) && comparison >= 0,
 | 
	
		
			
				|  |  | +                    Operator.IsLessThan => TryCompare(objValue, value, out var comparison) && comparison < 0,
 | 
	
		
			
				|  |  | +                    Operator.IsLessThanOrEqualTo => TryCompare(objValue, value, out var comparison) && comparison <= 0,
 | 
	
		
			
				|  |  | +                    Operator.BeginsWith => (objValue?.ToString() ?? "").StartsWith(value?.ToString() ?? ""),
 | 
	
		
			
				|  |  | +                    Operator.Contains => (objValue?.ToString() ?? "").Contains(value?.ToString() ?? ""),
 | 
	
		
			
				|  |  | +                    Operator.EndsWith => (objValue?.ToString() ?? "").EndsWith(value?.ToString() ?? ""),
 | 
	
		
			
				|  |  | +                    _ => throw new Exception($"Invalid operator {filter.Operator}"),
 | 
	
		
			
				|  |  | +                };
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private static bool MatchFilter<T>(this Filter<T> filter, T obj, IQueryProviderFactory query) =>
 | 
	
		
			
				|  |  | +            filter.IsNot ? !filter.MatchOperator(obj, query) : filter.MatchOperator(obj, query);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        private static bool TryCompare(object? a, object? b, out int result)
 | 
	
		
			
				|  |  | +        {
 | 
	
		
			
				|  |  | +            try
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                result = Comparer.Default.Compare(a, b);
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            catch (ArgumentException)
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                result = 0;
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        /// <summary>
 | 
	
		
			
				|  |  | +        /// Check if <paramref name="obj"/> matches this filter; that is, that the operators return true.
 | 
	
		
			
				|  |  | +        /// </summary>
 | 
	
		
			
				|  |  | +        /// <param name="query">The query provider used to evaluate <see cref="Operator.InQuery"/> filters.</param>
 | 
	
		
			
				|  |  | +        public static bool Match<T>(this Filter<T>? filter, T obj, IQueryProviderFactory query) =>
 | 
	
		
			
				|  |  | +            filter is null
 | 
	
		
			
				|  |  | +            || (filter.MatchFilter(obj, query) && filter.Ands.All(x => x.Match(obj, query)))
 | 
	
		
			
				|  |  | +            || filter.Ors.Any(x => x.Match(obj, query));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public class FilterJsonConverter : JsonConverter
 |