diff --git a/apps/fyle/constants.py b/apps/fyle/constants.py new file mode 100644 index 00000000..2171b9aa --- /dev/null +++ b/apps/fyle/constants.py @@ -0,0 +1,22 @@ +DEFAULT_FYLE_CONDITIONS = [ + { + 'field_name': 'employee_email', + 'type': 'SELECT', + 'is_custom': False + }, + { + 'field_name': 'claim_number', + 'type': 'TEXT', + 'is_custom': False + }, + { + 'field_name': 'report_title', + 'type': 'TEXT', + 'is_custom': False + }, + { + 'field_name': 'spent_at', + 'type': 'DATE', + 'is_custom': False + } +] diff --git a/apps/fyle/helpers.py b/apps/fyle/helpers.py index 6cfb45df..a65641d7 100644 --- a/apps/fyle/helpers.py +++ b/apps/fyle/helpers.py @@ -2,6 +2,11 @@ import requests from django.conf import settings +from fyle_integrations_platform_connector import PlatformConnector + +from apps.workspaces.models import FyleCredential +from apps.fyle.constants import DEFAULT_FYLE_CONDITIONS + def post_request(url, body, refresh_token=None): """ @@ -50,3 +55,27 @@ def get_cluster_domain(refresh_token: str) -> str: cluster_api_url = '{0}/oauth/cluster/'.format(settings.FYLE_BASE_URL) return post_request(cluster_api_url, {}, refresh_token)['cluster_domain'] + + +def get_expense_fields(workspace_id: int): + """ + Get expense custom fields from fyle + :param workspace_id: (int) + :return: list of custom expense fields + """ + + fyle_credentails = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentails) + custom_fields = platform.expense_custom_fields.list_all() + + response = [] + response.extend(DEFAULT_FYLE_CONDITIONS) + for custom_field in custom_fields: + if custom_field['type'] in ('SELECT', 'NUMBER', 'TEXT', 'BOOLEAN'): + response.append({ + 'field_name': custom_field['field_name'], + 'type': custom_field['type'], + 'is_custom': custom_field['is_custom'] + }) + + return response diff --git a/apps/fyle/models.py b/apps/fyle/models.py index e69de29b..a2ce7cec 100644 --- a/apps/fyle/models.py +++ b/apps/fyle/models.py @@ -0,0 +1,8 @@ + + +class Reimbursement: + """ + Creating a dummy class to be able to user + fyle_integrations_platform_connector correctly + """ + pass diff --git a/apps/fyle/serializers.py b/apps/fyle/serializers.py index e69de29b..5204823b 100644 --- a/apps/fyle/serializers.py +++ b/apps/fyle/serializers.py @@ -0,0 +1,20 @@ +from rest_framework import serializers + +from apps.fyle.helpers import get_expense_fields + + +class ExpenseFieldSerializer(serializers.Serializer): + """ + Workspace Admin Serializer + """ + expense_fields = serializers.SerializerMethodField() + + def get_expense_fields(self, validated_data): + """ + Get Expense Fields + """ + + workspace_id = self.context['request'].parser_context.get('kwargs').get('workspace_id') + expense_fields = get_expense_fields(workspace_id=workspace_id) + + return expense_fields diff --git a/apps/fyle/urls.py b/apps/fyle/urls.py index e69de29b..f06ca770 100644 --- a/apps/fyle/urls.py +++ b/apps/fyle/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from apps.fyle.views import CustomFieldView + +urlpatterns = [ + path('expense_fields/', CustomFieldView.as_view(), name='fyle-expense-fields'), +] diff --git a/apps/fyle/views.py b/apps/fyle/views.py index e69de29b..76600d64 100644 --- a/apps/fyle/views.py +++ b/apps/fyle/views.py @@ -0,0 +1,13 @@ +from rest_framework import generics + +from apps.workspaces.models import Workspace +from apps.fyle.serializers import ExpenseFieldSerializer + + +class CustomFieldView(generics.ListAPIView): + """ + Custom Field view + """ + + serializer_class = ExpenseFieldSerializer + queryset = Workspace.objects.all() diff --git a/apps/workspaces/urls.py b/apps/workspaces/urls.py index 502c5ee6..48bf5936 100644 --- a/apps/workspaces/urls.py +++ b/apps/workspaces/urls.py @@ -35,6 +35,7 @@ other_app_paths = [ path('/sage_300/', include('apps.sage300.urls')), + path('/fyle/', include('apps.fyle.urls')) ] urlpatterns = [] diff --git a/sage_desktop_api/settings.py b/sage_desktop_api/settings.py index e5c96f6e..1e12e8a1 100644 --- a/sage_desktop_api/settings.py +++ b/sage_desktop_api/settings.py @@ -176,8 +176,6 @@ } } - - CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', diff --git a/sage_desktop_api/tests/settings.py b/sage_desktop_api/tests/settings.py index d24f0082..2f274aa3 100644 --- a/sage_desktop_api/tests/settings.py +++ b/sage_desktop_api/tests/settings.py @@ -179,11 +179,9 @@ FYLE_APP_URL = os.environ.get('APP_URL') FYLE_EXPENSE_URL = os.environ.get('FYLE_APP_URL') - SD_API_KEY = os.environ.get('SD_API_KEY') SD_API_SECRET = os.environ.get('SD_API_SECRET') - # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ diff --git a/tests/helper.py b/tests/helper.py new file mode 100644 index 00000000..9a5e2cea --- /dev/null +++ b/tests/helper.py @@ -0,0 +1,38 @@ +import json +from os import path + + +def dict_compare_keys(d1, d2, key_path=''): + """ + Compare two dicts recursively and see if dict1 has any keys that dict2 does not + Returns: list of key paths + """ + res = [] + if not d1: + return res + if not isinstance(d1, dict): + return res + for k in d1: + if k not in d2: + missing_key_path = f'{key_path}->{k}' + res.append(missing_key_path) + else: + if isinstance(d1[k], dict): + key_path1 = f'{key_path}->{k}' + res1 = dict_compare_keys(d1[k], d2[k], key_path1) + res = res + res1 + elif isinstance(d1[k], list): + key_path1 = f'{key_path}->{k}[0]' + dv1 = d1[k][0] if len(d1[k]) > 0 else None + dv2 = d2[k][0] if len(d2[k]) > 0 else None + res1 = dict_compare_keys(dv1, dv2, key_path1) + res = res + res1 + return res + + +def get_response_dict(filename): + basepath = path.dirname(__file__) + filepath = path.join(basepath, filename) + mock_json = open(filepath, 'r').read() + mock_dict = json.loads(mock_json) + return mock_dict diff --git a/tests/test_fyle/fixtures.py b/tests/test_fyle/fixtures.py index 95dea99b..a638f882 100644 --- a/tests/test_fyle/fixtures.py +++ b/tests/test_fyle/fixtures.py @@ -1,27 +1,63 @@ fixtures = { - 'get_my_profile': { - 'data': { - 'org': { - 'currency': 'USD', - 'domain': 'fyleforqvd.com', - 'id': 'orNoatdUnm1w', - 'name': 'Fyle For MS Dynamics Demo', + "get_my_profile": { + "data": { + "org": { + "currency": "USD", + "domain": "fyleforqvd.com", + "id": "orNoatdUnm1w", + "name": "Fyle For MS Dynamics Demo", }, - 'org_id': 'orNoatdUnm1w', - 'roles': [ - 'FYLER', - 'VERIFIER', - 'PAYMENT_PROCESSOR', - 'FINANCE', - 'ADMIN', - 'AUDITOR', + "org_id": "orNoatdUnm1w", + "roles": [ + "FYLER", + "VERIFIER", + "PAYMENT_PROCESSOR", + "FINANCE", + "ADMIN", + "AUDITOR", ], - 'user': { - 'email': 'ashwin.t@fyle.in', - 'full_name': 'Joanna', - 'id': 'usqywo0f3nBY' + "user": { + "email": "ashwin.t@fyle.in", + "full_name": "Joanna", + "id": "usqywo0f3nBY", }, - 'user_id': 'usqywo0f3nBY', + "user_id": "usqywo0f3nBY", } - } + }, + "fyle_expense_custom_fields": [ + {"field_name": "employee_email", "type": "SELECT", "is_custom": False}, + {"field_name": "claim_number", "type": "TEXT", "is_custom": False}, + {"field_name": "report_title", "type": "TEXT", "is_custom": False}, + {"field_name": "spent_at", "type": "DATE", "is_custom": False}, + {"field_name": "Class", "type": "SELECT", "is_custom": True}, + {"field_name": "Fyle Categories", "type": "SELECT", "is_custom": True}, + {"field_name": "Operating System", "type": "SELECT", "is_custom": True}, + {"field_name": "User Dimension", "type": "SELECT", "is_custom": True}, + {"field_name": "Asdasdas", "type": "SELECT", "is_custom": True}, + {"field_name": "Nilesh Custom Field", "type": "SELECT", "is_custom": True}, + ], + "get_all_custom_fields": [ + { + "data": [ + { + "category_ids": [142151], + "code": None, + "column_name": "text_column6", + "created_at": "2021-10-22T07:50:04.613487+00:00", + "default_value": None, + "field_name": "Class", + "id": 197380, + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "options": ["Servers", "Home", "Office"], + "org_id": "orGcBCVPijjO", + "placeholder": "Select Class", + "seq": 1, + "type": "SELECT", + "updated_at": "2023-01-01T05:35:26.345303+00:00", + }, + ] + } + ], } diff --git a/tests/test_fyle/test_views.py b/tests/test_fyle/test_views.py new file mode 100644 index 00000000..c0481c0d --- /dev/null +++ b/tests/test_fyle/test_views.py @@ -0,0 +1,36 @@ +import pytest +import json +from django.urls import reverse + +from tests.helper import dict_compare_keys +from tests.test_fyle.fixtures import fixtures as fyle_fixtures + + +@pytest.mark.django_db(databases=['default']) +def test_fyle_expense_fields( + api_client, + test_connection, + create_temp_workspace, + add_fyle_credentials, + add_sage300_creds, + mocker, +): + workspace_id = 1 + + access_token = test_connection.access_token + url = reverse('fyle-expense-fields', kwargs={'workspace_id': workspace_id}) + + mocker.patch( + 'fyle.platform.apis.v1beta.admin.ExpenseFields.list_all', + return_value=fyle_fixtures['get_all_custom_fields'], + ) + + api_client.credentials(HTTP_AUTHORIZATION="Bearer {}".format(access_token)) + + response = api_client.get(url) + assert response.status_code == 200 + response = json.loads(response.content) + + assert ( + dict_compare_keys(response['results'], fyle_fixtures['fyle_expense_custom_fields']) == [] + ), 'expense group api return diffs in keys'