Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fyle fields api #22

Merged
merged 5 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions apps/fyle/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,18 @@
Fyle Serializers
"""
import logging
from datetime import datetime, timezone
from django.db.models import Q
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from rest_framework.views import status
from fyle_integrations_platform_connector import PlatformConnector

from fyle_accounting_mappings.models import ExpenseAttribute

from apps.fyle.models import ExpenseFilter
from apps.workspaces.models import Workspace, FyleCredential

logger = logging.getLogger(__name__)
logger.level = logging.INFO
Expand All @@ -19,3 +28,80 @@ class Meta:
model = ExpenseFilter
fields = '__all__'
read_only_fields = ('id', 'workspace', 'created_at', 'updated_at')


class ImportFyleAttributesSerializer(serializers.Serializer):
"""
Import Fyle Attributes serializer
"""

def create(self, validated_data):
"""
Import Fyle Attributes
"""
try:
workspace_id = self.context['request'].parser_context.get('kwargs').get('workspace_id')
refresh = self.context['request'].data.get('refresh')

workspace = Workspace.objects.get(id=workspace_id)
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials)

if refresh:
platform.import_fyle_dimensions()
workspace.source_synced_at = datetime.now()
workspace.save(update_fields=['source_synced_at'])

else:
if workspace.source_synced_at:
time_interval = datetime.now(timezone.utc) - workspace.source_synced_at

if workspace.source_synced_at is None or time_interval.days > 0:
platform.import_fyle_dimensions()
workspace.source_synced_at = datetime.now()
workspace.save(update_fields=['source_synced_at'])

return Response(status=status.HTTP_200_OK)

except FyleCredential.DoesNotExist:
raise serializers.ValidationError({'message': 'Fyle credentials not found in workspace'}, code='invalid_login')

except Exception as exception:
logger.error('Something unexpected happened workspace_id: %s %s', workspace_id, exception)
raise APIException("Internal Server Error", code='server_error')


class FyleFieldsSerializer(serializers.Serializer):
"""
Fyle Fields Serializer
"""

attribute_type = serializers.CharField()
display_name = serializers.CharField()
is_dependant = serializers.BooleanField()

def format_fyle_fields(self, workspace_id):
"""
Get Fyle Fields
"""

attribute_types = ['EMPLOYEE', 'CATEGORY', 'PROJECT', 'COST_CENTER', 'TAX_GROUP', 'CORPORATE_CARD', 'MERCHANT']

attributes = ExpenseAttribute.objects.filter(
~Q(attribute_type__in=attribute_types),
workspace_id=workspace_id
).values('attribute_type', 'display_name', 'detail__is_dependent').distinct()

attributes_list = [
{'attribute_type': 'COST_CENTER', 'display_name': 'Cost Center', 'is_dependant': False},
{'attribute_type': 'PROJECT', 'display_name': 'Project', 'is_dependant': False}
]

for attr in attributes:
attributes_list.append({
'attribute_type': attr['attribute_type'],
'display_name': attr['display_name'],
'is_dependant': attr['detail__is_dependent']
})

return attributes_list
4 changes: 3 additions & 1 deletion apps/fyle/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
"""

from django.urls import path
from apps.fyle.views import ExpenseFilterView, ExpenseFilterDeleteView
from apps.fyle.views import ExpenseFilterView, ExpenseFilterDeleteView, ImportFyleAttributesView, FyleFieldsView


urlpatterns = [
path('expense_filters/<int:pk>/', ExpenseFilterDeleteView.as_view(), name='expense-filters'),
path('expense_filters/', ExpenseFilterView.as_view(), name='expense-filters'),
path('import_attributes/', ImportFyleAttributesView.as_view(), name='import-fyle-attributes'),
path('fields/', FyleFieldsView.as_view(), name='fyle-fields'),
]
20 changes: 19 additions & 1 deletion apps/fyle/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from rest_framework import generics
from ms_business_central_api.utils import LookupFieldMixin
from apps.fyle.serializers import ExpenseFilterSerializer
from apps.fyle.serializers import ExpenseFilterSerializer, ImportFyleAttributesSerializer, FyleFieldsSerializer
from apps.fyle.models import ExpenseFilter

logger = logging.getLogger(__name__)
Expand All @@ -24,3 +24,21 @@ class ExpenseFilterDeleteView(generics.DestroyAPIView):

queryset = ExpenseFilter.objects.all()
serializer_class = ExpenseFilterSerializer


class ImportFyleAttributesView(generics.CreateAPIView):
"""
Import Fyle Attributes View
"""
serializer_class = ImportFyleAttributesSerializer


class FyleFieldsView(generics.ListAPIView):
"""
Fyle Fields view
"""

serializer_class = FyleFieldsSerializer

def get_queryset(self):
return FyleFieldsSerializer().format_fyle_fields(self.kwargs["workspace_id"])
12 changes: 12 additions & 0 deletions tests/test_fyle/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,16 @@
},
],
},
"fyle_fields_response": [
{
'attribute_type': 'COST_CENTER',
'display_name': 'Cost Center',
'is_dependant': False
},
{
'attribute_type': 'PROJECT',
'display_name': 'Project',
'is_dependant': False
}
],
}
54 changes: 54 additions & 0 deletions tests/test_fyle/test_views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import json
from unittest import mock
from django.urls import reverse
from apps.workspaces.models import FyleCredential, Workspace
from tests.helpers import dict_compare_keys
from .fixtures import fixtures as data

Expand All @@ -24,3 +26,55 @@ def test_expense_filters(api_client, test_connection, create_temp_workspace, add
response = api_client.get(url)
assert response.status_code == 200
assert dict_compare_keys(response, data['expense_filters_response']['results'][1]) == [], 'expense group api return diffs in keys'


def test_import_fyle_attributes(mocker, api_client, test_connection, create_temp_workspace, add_fyle_credentials):
mocker.patch('fyle_integrations_platform_connector.fyle_integrations_platform_connector.PlatformConnector.import_fyle_dimensions', return_value=[])

url = reverse('import-fyle-attributes', kwargs={'workspace_id': 1})

api_client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(test_connection.access_token))

payload = {'refresh': True}

response = api_client.post(url, payload)
assert response.status_code == 201

payload = {'refresh': False}

response = api_client.post(url, payload)
assert response.status_code == 201

workspace = Workspace.objects.get(id=1)
workspace.source_synced_at = None
workspace.save()

response = api_client.post(url, payload)
assert response.status_code == 201

fyle_credentials = FyleCredential.objects.get(workspace_id=1)
fyle_credentials.delete()

response = api_client.post(url, payload)
assert response.status_code == 400
assert response.data['message'] == 'Fyle credentials not found in workspace'

with mock.patch('apps.workspaces.models.FyleCredential.objects.get') as mock_call:
mock_call.side_effect = Exception()
response = api_client.post(url, payload)
assert response.status_code == 500


def test_fyle_fields(api_client, test_connection, create_temp_workspace, add_fyle_credentials):

access_token = test_connection.access_token

url = reverse('fyle-fields', kwargs={'workspace_id': 1})

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 response['results'] == data['fyle_fields_response']
Loading