Skip to content

Commit

Permalink
Add support for editing expenses (#606)
Browse files Browse the repository at this point in the history
* Add support for editing expenses

* change log
  • Loading branch information
Hrishabh17 authored Jun 26, 2024
1 parent bce19ab commit ed81a64
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 18 deletions.
97 changes: 97 additions & 0 deletions apps/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import logging

from fyle.platform.exceptions import InvalidTokenError as FyleInvalidTokenError
from fyle.platform.exceptions import NoPrivilegeError
from rest_framework.response import Response
from rest_framework.views import status
from rest_framework.exceptions import ValidationError

from apps.fyle.models import ExpenseGroup
from apps.mappings.models import GeneralMapping
from apps.workspaces.models import FyleCredential, NetSuiteCredentials, Workspace, Configuration

logger = logging.getLogger(__name__)
logger.level = logging.INFO


def handle_view_exceptions():
def decorator(func):
def new_fn(*args, **kwargs):
try:
return func(*args, **kwargs)
except ExpenseGroup.DoesNotExist:
return Response(
data={'message': 'Expense group not found'},
status=status.HTTP_400_BAD_REQUEST
)

except FyleCredential.DoesNotExist:
return Response(
data={'message': 'Fyle credentials not found in workspace'},
status=status.HTTP_400_BAD_REQUEST
)

except FyleInvalidTokenError as exception:
logger.info(
'Fyle token expired workspace_id - %s %s',
kwargs['workspace_id'],
{'error': exception.response}
)
return Response(
data={'message': 'Fyle token expired workspace_id'},
status=status.HTTP_400_BAD_REQUEST
)

except GeneralMapping.DoesNotExist:
return Response(
{'message': 'General mappings do not exist for the workspace'},
status=status.HTTP_400_BAD_REQUEST
)

except NoPrivilegeError as exception:
logger.info(
'Invalid Fyle Credentials / Admin is disabled for workspace_id%s %s',
kwargs['workspace_id'],
{'error': exception.response}
)
return Response(
data={'message': 'Invalid Fyle Credentials / Admin is disabled'},
status=status.HTTP_400_BAD_REQUEST
)

except Workspace.DoesNotExist:
return Response(
data={'message': 'Workspace with this id does not exist'},
status=status.HTTP_400_BAD_REQUEST
)

except Configuration.DoesNotExist:
return Response(
data={'message': 'Configuration does not exist in workspace'},
status=status.HTTP_400_BAD_REQUEST
)

except NetSuiteCredentials.DoesNotExist:
logger.info('Netsuite credentials not found in workspace')
return Response(
data={'message': 'Netsuite credentials not found in workspace'},
status=status.HTTP_400_BAD_REQUEST
)

except ValidationError as e:
logger.exception(e)
return Response(
{"message": e.detail},
status=status.HTTP_400_BAD_REQUEST
)

except Exception as exception:
logger.exception(exception)
return Response(
data={'message': 'An unhandled error has occurred, please re-try later'},
status=status.HTTP_400_BAD_REQUEST
)

return new_fn

return decorator
13 changes: 12 additions & 1 deletion apps/fyle/helpers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import json
import logging
import traceback
import requests
from datetime import datetime, timezone
from fyle_integrations_platform_connector import PlatformConnector
import logging

from django.conf import settings
from django.db.models import Q
from rest_framework.exceptions import ValidationError
from fyle_accounting_mappings.models import ExpenseAttribute

from apps.fyle.models import ExpenseGroupSettings, ExpenseFilter, ExpenseGroup, Expense
Expand Down Expand Up @@ -397,6 +398,16 @@ def construct_expense_filter(expense_filter: ExpenseFilter):
return constructed_expense_filter


def assert_valid_request(workspace_id:int, fyle_org_id:str):
"""
Assert if the request is valid by checking
the url_workspace_id and fyle_org_id workspace
"""
workspace = Workspace.objects.get(fyle_org_id=fyle_org_id)
if workspace.id != workspace_id:
raise ValidationError('Workspace mismatch')


class AdvanceSearchFilter(django_filters.FilterSet):
def filter_queryset(self, queryset):
or_filtered_queryset = queryset.none()
Expand Down
14 changes: 13 additions & 1 deletion apps/fyle/queue.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import logging
from django_q.tasks import async_task
from apps.fyle.helpers import assert_valid_request

logger = logging.getLogger(__name__)
logger.level = logging.INFO


def async_post_accounting_export_summary(org_id: str, workspace_id: int) -> None:
Expand All @@ -12,7 +17,7 @@ def async_post_accounting_export_summary(org_id: str, workspace_id: int) -> None
async_task('apps.fyle.tasks.post_accounting_export_summary', org_id, workspace_id)


def async_import_and_export_expenses(body: dict) -> None:
def async_import_and_export_expenses(body: dict, workspace_id: int) -> None:
"""
Async'ly import and export expenses
:param body: body
Expand All @@ -21,4 +26,11 @@ def async_import_and_export_expenses(body: dict) -> None:
if body.get('action') == 'ACCOUNTING_EXPORT_INITIATED' and body.get('data'):
report_id = body['data']['id']
org_id = body['data']['org_id']
assert_valid_request(workspace_id=workspace_id, fyle_org_id=org_id)
async_task('apps.fyle.tasks.import_and_export_expenses', report_id, org_id)

elif body.get('action') == 'UPDATED_AFTER_APPROVAL' and body.get('data') and body.get('resource') == 'EXPENSE':
org_id = body['data']['org_id']
logger.info("| Updating non-exported expenses through webhook | Content: {{WORKSPACE_ID: {} Payload: {}}}".format(workspace_id, body.get('data')))
assert_valid_request(workspace_id=workspace_id, fyle_org_id=org_id)
async_task('apps.fyle.tasks.update_non_exported_expenses', body['data'])
26 changes: 26 additions & 0 deletions apps/fyle/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django_q.tasks import async_task

from fyle_integrations_platform_connector import PlatformConnector
from fyle_integrations_platform_connector.apis.expenses import Expenses as FyleExpenses
from fyle.platform.exceptions import (
RetryException,
InternalServerError,
Expand Down Expand Up @@ -293,3 +294,28 @@ def import_and_export_expenses(report_id: str, org_id: str) -> None:

except Exception:
handle_import_exception(task_log)


def update_non_exported_expenses(data: Dict) -> None:
"""
To update expenses not in COMPLETE, IN_PROGRESS state
"""
expense_state = None
org_id = data['org_id']
expense_id = data['id']
workspace = Workspace.objects.get(fyle_org_id=org_id)
expense = Expense.objects.filter(workspace_id=workspace.id, expense_id=expense_id).first()

if expense:
if 'state' in expense.accounting_export_summary:
expense_state = expense.accounting_export_summary['state']
else:
expense_state = 'NOT_EXPORTED'

if expense_state and expense_state not in ['COMPLETE', 'IN_PROGRESS']:
expense_obj = []
expense_obj.append(data)
expense_objects = FyleExpenses().construct_expense_object(expense_obj, expense.workspace_id)
Expense.create_expense_objects(
expense_objects, expense.workspace_id
)
17 changes: 9 additions & 8 deletions apps/fyle/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from fyle_accounting_mappings.models import ExpenseAttribute
from fyle_accounting_mappings.serializers import ExpenseAttributeSerializer

from apps.workspaces.models import Configuration, FyleCredential, Workspace
from apps.workspaces.models import FyleCredential, Workspace
from fyle_netsuite_api.utils import LookupFieldMixin

from .tasks import schedule_expense_group_creation, get_task_log_and_fund_source, create_expense_groups
Expand All @@ -24,8 +24,7 @@
from .queue import async_import_and_export_expenses
from .constants import DEFAULT_FYLE_CONDITIONS

from fyle.platform import Platform
from fyle_netsuite_api import settings
from apps.exceptions import handle_view_exceptions

from django_filters.rest_framework import DjangoFilterBackend

Expand All @@ -40,6 +39,7 @@ class ExpenseGroupViewV2(LookupFieldMixin, generics.ListCreateAPIView):
filter_backends = (DjangoFilterBackend,)
filterset_class = ExpenseGroupSearchFilter


class ExpenseViewV2(LookupFieldMixin, generics.ListAPIView):
"""
Expense view
Expand Down Expand Up @@ -281,13 +281,13 @@ def post(self, request, *args, **kwargs):
},
status=status.HTTP_400_BAD_REQUEST
)
except Exception :
except Exception:
return Response(
data={
'message': 'Error in syncing Dimensions'
},
status=status.HTTP_400_BAD_REQUEST
)
)


class RefreshFyleDimensionView(generics.ListCreateAPIView):
Expand Down Expand Up @@ -344,7 +344,7 @@ def delete(self, request, *args, **kwargs):

return Response(data={
'workspace_id': workspace_id,
'rank' : rank,
'rank': rank,
'message': 'Expense filter deleted'
})

Expand Down Expand Up @@ -400,7 +400,7 @@ def get(self, request, *args, **kwargs):
platform = PlatformConnector(fyle_credentails)
custom_fields = platform.expense_custom_fields.list_all()

response = []
response = []
response.extend(DEFAULT_FYLE_CONDITIONS)

for custom_field in custom_fields:
Expand Down Expand Up @@ -456,7 +456,8 @@ class ExportView(generics.CreateAPIView):
authentication_classes = []
permission_classes = []

@handle_view_exceptions()
def post(self, request, *args, **kwargs):
async_import_and_export_expenses(request.data)
async_import_and_export_expenses(request.data, int(kwargs['workspace_id']))

return Response(data={}, status=status.HTTP_200_OK)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ enum34==1.1.10
future==0.18.2
fyle==0.37.0
fyle-accounting-mappings==1.32.2
fyle-integrations-platform-connector==1.38.0
fyle-integrations-platform-connector==1.38.1
fyle-rest-auth==1.7.2
gunicorn==20.1.0
gevent==23.9.1
Expand Down
Loading

0 comments on commit ed81a64

Please sign in to comment.