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

Add support for editing expenses #199

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions apps/fyle/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from fyle.platform.exceptions import NoPrivilegeError, RetryException, InvalidTokenError as FyleInvalidTokenError
from rest_framework.response import Response
from rest_framework.views import status
from rest_framework.exceptions import ValidationError

from sage_desktop_sdk.exceptions.hh2_exceptions import WrongParamsError
from sage_desktop_api.exceptions import BulkError
Expand Down Expand Up @@ -90,6 +91,10 @@ def new_fn(*args, **kwargs):
logger.info('Bulk Error %s', exception.response)
return Response(data={'message': 'Bulk Error'}, 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)
Expand Down
13 changes: 12 additions & 1 deletion apps/fyle/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
from django.db.models import Q

from fyle_integrations_platform_connector import PlatformConnector
from rest_framework.exceptions import ValidationError

from apps.workspaces.models import FyleCredential, ExportSetting
from apps.workspaces.models import Workspace, FyleCredential, ExportSetting
from apps.accounting_exports.models import AccountingExport
from apps.fyle.models import ExpenseFilter
from apps.fyle.constants import DEFAULT_FYLE_CONDITIONS
Expand Down Expand Up @@ -255,3 +256,13 @@ def get_exportable_accounting_exports_ids(workspace_id: int):
).values_list('id', flat=True)

return accounting_export_ids


def assert_valid_request(workspace_id:int, org_id:str):
"""
Assert if the request is valid by checking
the url_workspace_id and fyle_org_id workspace
"""
workspace = Workspace.objects.get(org_id=org_id)
if workspace.id != workspace_id:
raise ValidationError('Workspace mismatch')
Comment on lines +261 to +268
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the case where no workspace matches the given org_id.

Currently, if no workspace is found, a Workspace.DoesNotExist exception will be raised. It would be more user-friendly to catch this exception and raise a ValidationError with a clear message.

- workspace = Workspace.objects.get(org_id=org_id)
+ try:
+     workspace = Workspace.objects.get(org_id=org_id)
+ except Workspace.DoesNotExist:
+     raise ValidationError(f"No workspace found for org_id {org_id}")
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def assert_valid_request(workspace_id:int, org_id:str):
"""
Assert if the request is valid by checking
the url_workspace_id and fyle_org_id workspace
"""
workspace = Workspace.objects.get(org_id=org_id)
if workspace.id != workspace_id:
raise ValidationError('Workspace mismatch')
def assert_valid_request(workspace_id:int, org_id:str):
"""
Assert if the request is valid by checking
the url_workspace_id and fyle_org_id workspace
"""
try:
workspace = Workspace.objects.get(org_id=org_id)
except Workspace.DoesNotExist:
raise ValidationError(f"No workspace found for org_id {org_id}")
if workspace.id != workspace_id:
raise ValidationError('Workspace mismatch')

14 changes: 13 additions & 1 deletion apps/fyle/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* User Triggered Async Tasks
* Schedule Triggered Async Tasks
"""
import logging
from django_q.tasks import async_task

from apps.fyle.tasks import (
Expand All @@ -11,6 +12,10 @@
)
from apps.accounting_exports.models import AccountingExport
from apps.workspaces.models import Workspace
from apps.fyle.helpers import assert_valid_request

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


def queue_import_reimbursable_expenses(workspace_id: int, synchronous: bool = False):
Expand Down Expand Up @@ -61,7 +66,7 @@ def queue_import_credit_card_expenses(workspace_id: int, synchronous: bool = Fal
import_credit_card_expenses(workspace_id, accounting_export)


def async_handle_webhook_callback(body: dict) -> None:
def async_handle_webhook_callback(body: dict, workspace_id: int) -> None:
"""
Async'ly import and export expenses
:param body: body
Expand All @@ -70,7 +75,14 @@ def async_handle_webhook_callback(body: dict) -> None:
if body.get('action') == 'ACCOUNTING_EXPORT_INITIATED' and body.get('data'):
org_id = body['data']['org_id']

assert_valid_request(workspace_id=workspace_id, org_id=org_id)
workspace = Workspace.objects.get(org_id=org_id)
queue_import_reimbursable_expenses(workspace_id=workspace.id)
queue_import_credit_card_expenses(workspace_id=workspace.id)
async_task('apps.workspaces.tasks.export_to_sage300', workspace.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, org_id=org_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move out

async_task('apps.fyle.tasks.update_non_exported_expenses', body['data'])
27 changes: 27 additions & 0 deletions apps/fyle/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
"""
import logging
from datetime import datetime
from typing import Dict
from django.db import transaction

from fyle_integrations_platform_connector import PlatformConnector
from fyle_integrations_platform_connector.apis.expenses import Expenses as FyleExpenses

from apps.accounting_exports.models import AccountingExport
from apps.workspaces.models import ExportSetting, Workspace, FyleCredential
Expand Down Expand Up @@ -120,3 +122,28 @@ def import_credit_card_expenses(workspace_id, accounting_export: AccountingExpor
:param workspace_id: workspace id
"""
import_expenses(workspace_id, accounting_export, 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT', 'CCC')


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(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
)
2 changes: 1 addition & 1 deletion apps/fyle/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,6 @@ class WebhookCallbackView(generics.CreateAPIView):

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

return Response(data={}, status=status.HTTP_200_OK)
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ gevent==23.9.1
gunicorn==20.1.0

# Platform SDK
fyle==0.36.1
fyle==0.37.0

# Reusable Fyle Packages
fyle-rest-auth==1.7.2
fyle-accounting-mappings==1.33.1
fyle-integrations-platform-connector==1.37.4
fyle-integrations-platform-connector==1.38.1


# Postgres Dependincies
Expand Down
Loading
Loading