diff --git a/apps/fyle/constants.py b/apps/fyle/constants.py index 2171b9aa..4c5c56ac 100644 --- a/apps/fyle/constants.py +++ b/apps/fyle/constants.py @@ -18,5 +18,10 @@ 'field_name': 'spent_at', 'type': 'DATE', 'is_custom': False + }, + { + 'field_name': 'category', + 'type': 'SELECT', + 'is_custom': False } ] diff --git a/apps/fyle/helpers.py b/apps/fyle/helpers.py index 59dddaa8..4f5277f7 100644 --- a/apps/fyle/helpers.py +++ b/apps/fyle/helpers.py @@ -60,7 +60,14 @@ def construct_expense_filter(expense_filter): else: # If the isnull filter value is False, invert the exact filter using the ~Q operator and assign it to the constructed expense filter constructed_expense_filter = ~Q(**filter2) - + # for category non-custom field with not_in as operator, to check this later on + elif expense_filter.condition == 'category' and expense_filter.operator == 'not_in' and not expense_filter.is_custom: + # construct the filter + filter1 = { + f'{expense_filter.condition}__in': expense_filter.values + } + # Invert the filter using the ~Q operator and assign it to the constructed expense filter + constructed_expense_filter = ~Q(**filter1) # For all non-custom fields else: # Construct the filter for the non-custom field diff --git a/apps/fyle/migrations/0003_alter_expensefilter_custom_field_type.py b/apps/fyle/migrations/0003_alter_expensefilter_custom_field_type.py new file mode 100644 index 00000000..e5ed233d --- /dev/null +++ b/apps/fyle/migrations/0003_alter_expensefilter_custom_field_type.py @@ -0,0 +1,19 @@ +# Generated by Django 4.1.2 on 2024-05-14 14:18 + +from django.db import migrations +import sage_desktop_api.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('fyle', '0002_expense_is_skipped'), + ] + + operations = [ + migrations.AlterField( + model_name='expensefilter', + name='custom_field_type', + field=sage_desktop_api.models.fields.StringOptionsField(choices=[('SELECT', 'SELECT'), ('NUMBER', 'NUMBER'), ('TEXT', 'TEXT'), ('BOOLEAN', 'BOOLEAN')], default='', help_text='Custom field type', max_length=255, null=True), + ), + ] diff --git a/apps/fyle/models.py b/apps/fyle/models.py index ac1c982f..82766e64 100644 --- a/apps/fyle/models.py +++ b/apps/fyle/models.py @@ -32,7 +32,8 @@ EXPENSE_FILTER_CUSTOM_FIELD_TYPE = ( ('SELECT', 'SELECT'), ('NUMBER', 'NUMBER'), - ('TEXT', 'TEXT') + ('TEXT', 'TEXT'), + ('BOOLEAN', 'BOOLEAN') ) EXPENSE_FILTER_OPERATOR = ( diff --git a/tests/test_fyle/test_helpers.py b/tests/test_fyle/test_helpers.py index 12f44c05..215b95d4 100644 --- a/tests/test_fyle/test_helpers.py +++ b/tests/test_fyle/test_helpers.py @@ -1,5 +1,8 @@ +import pytest from requests import Response from apps.fyle.helpers import ( + Q, + construct_expense_filter, get_fyle_orgs, get_request, post_request, @@ -139,3 +142,199 @@ def test_construct_expense_filter_query( returned_filter = construct_expense_filter_query(expense_filters=expense_payload_req) assert str(returned_filter) == "(OR: ('custom_properties__some_field__isnull', True), ('custom_properties__some_field__exact', None), ('custom_properties__employee_id__not_in', [12, 13]), ('custom_properties__is_email_sent__not_in', False))" + + +@pytest.mark.django_db() +def test_construct_expense_filter(): + # employee-email-is-equal + expense_filter = ExpenseFilter(condition='employee_email', operator='in', values=['killua.z@fyle.in', 'naruto.u@fyle.in'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'employee_email__in': ['killua.z@fyle.in', 'naruto.u@fyle.in']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # employee-email-is-equal-one-email-only + expense_filter = ExpenseFilter(condition='employee_email', operator='in', values=['killua.z@fyle.in'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'employee_email__in': ['killua.z@fyle.in']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # claim-number-is-equal + expense_filter = ExpenseFilter(condition='claim_number', operator='in', values=['ajdnwjnadw', 'ajdnwjnlol'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'claim_number__in': ['ajdnwjnadw', 'ajdnwjnlol']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # claim-number-is-equal-one-claim_number-only + expense_filter = ExpenseFilter(condition='claim_number', operator='in', values=['ajdnwjnadw'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'claim_number__in': ['ajdnwjnadw']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # report-name-is-equal + expense_filter = ExpenseFilter(condition='report_title', operator='iexact', values=['#17: Dec 2022'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'report_title__iexact': '#17: Dec 2022'} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # report-name-contains + expense_filter = ExpenseFilter(condition='report_title', operator='icontains', values=['Dec 2022'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'report_title__icontains': 'Dec 2022'} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # spent-at-is-before + expense_filter = ExpenseFilter(condition='spent_at', operator='lt', values=['2020-04-20 23:59:59+00'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'spent_at__lt': '2020-04-20 23:59:59+00'} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # spent-at-is-on-or-before + expense_filter = ExpenseFilter(condition='spent_at', operator='lte', values=['2020-04-20 23:59:59+00'], rank=1) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'spent_at__lte': '2020-04-20 23:59:59+00'} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # category_in + expense_filter = ExpenseFilter( + condition = 'category', + operator = 'in', + values = ['anish'], + rank = 1 + ) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'category__in':['anish']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # category_not_in + expense_filter = ExpenseFilter( + condition = 'category', + operator = 'not_in', + values = ['anish', 'singh'], + rank = 1 + ) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'category__in':['anish', 'singh']} + response = ~Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-number-is-equal + expense_filter = ExpenseFilter(condition='Gon Number', operator='in', values=[102, 108], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Gon Number__in': [102, 108]} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-number-is-not-empty + expense_filter = ExpenseFilter(condition='Gon Number', operator='isnull', values=['False'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Gon Number__exact': None} + response = ~Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-number-is--empty + expense_filter = ExpenseFilter(condition='Gon Number', operator='isnull', values=['True'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Gon Number__isnull': True} + filter_2 = {'custom_properties__Gon Number__exact': None} + response = Q(**filter_1) | Q(**filter_2) + + assert constructed_expense_filter == response + + # custom-properties-text-is-equal + expense_filter = ExpenseFilter(condition='Killua Text', operator='in', values=['hunter', 'naruto', 'sasuske'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Killua Text__in': ['hunter', 'naruto', 'sasuske']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-text-is-not-empty + expense_filter = ExpenseFilter(condition='Killua Text', operator='isnull', values=['False'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Killua Text__exact': None} + response = ~Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-text-is--empty + expense_filter = ExpenseFilter(condition='Killua Text', operator='isnull', values=['True'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Killua Text__isnull': True} + filter_2 = {'custom_properties__Killua Text__exact': None} + response = Q(**filter_1) | Q(**filter_2) + + assert constructed_expense_filter == response + + # custom-properties-select-is-equal + expense_filter = ExpenseFilter(condition='Kratos', operator='in', values=['BOOK', 'Dev-D'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Kratos__in': ['BOOK', 'Dev-D']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-select-is-equal-one-value + expense_filter = ExpenseFilter(condition='Kratos', operator='in', values=['BOOK'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Kratos__in': ['BOOK']} + response = Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-select-is-not-empty + expense_filter = ExpenseFilter(condition='Kratos', operator='isnull', values=['False'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Kratos__exact': None} + response = ~Q(**filter_1) + + assert constructed_expense_filter == response + + # custom-properties-select-is--empty + expense_filter = ExpenseFilter(condition='Kratos', operator='isnull', values=['True'], rank=1, is_custom=True) + constructed_expense_filter = construct_expense_filter(expense_filter) + + filter_1 = {'custom_properties__Kratos__isnull': True} + filter_2 = {'custom_properties__Kratos__exact': None} + response = Q(**filter_1) | Q(**filter_2) + + assert constructed_expense_filter == response