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

Expenses Sync APIs #32

Merged
merged 15 commits into from
Nov 30, 2023
Merged

Expenses Sync APIs #32

merged 15 commits into from
Nov 30, 2023

Conversation

ruuushhh
Copy link
Contributor

@ruuushhh ruuushhh commented Nov 20, 2023

Summary by CodeRabbit

  • New Features

    • Search functionality now available with a new search bar for improved navigation.
    • Synchronization of accounting exports and expense groups for better data management.
    • New import tasks and schedules to streamline data integration processes.
  • Enhancements

    • Improved error handling for more robust and reliable operations.
    • Enhanced import log tracking for better visibility into data synchronization.
    • User profile and organization retrieval now more efficient and user-friendly.
  • Bug Fixes

    • Fixed issues with expense grouping and accounting export creation.
    • Corrected parameter naming and logic in data synchronization methods.
    • Resolved discrepancies in workspace onboarding states for a smoother setup experience.
  • Documentation

    • Updated URL patterns for clearer API endpoint navigation.
    • Enhanced test cases for comprehensive coverage of new features and fixes.

Copy link

Tests Skipped Failures Errors Time
18 0 💤 0 ❌ 1 🔥 2.119s ⏱️

Copy link

Tests Skipped Failures Errors Time
19 0 💤 0 ❌ 0 🔥 2.291s ⏱️

@codecov-commenter
Copy link

Codecov Report

Attention: 21 lines in your changes are missing coverage. Please review.

Comparison is base (c8f1d9e) 87.74% compared to head (8d18a8a) 86.87%.

Files Patch % Lines
apps/fyle/tasks.py 68.96% 9 Missing ⚠️
apps/fyle/exceptions.py 72.41% 8 Missing ⚠️
apps/fyle/queue.py 73.33% 4 Missing ⚠️
Additional details and impacted files
@@                    Coverage Diff                     @@
##           exportable-expense-api      #32      +/-   ##
==========================================================
- Coverage                   87.74%   86.87%   -0.88%     
==========================================================
  Files                          25       28       +3     
  Lines                        1126     1211      +85     
==========================================================
+ Hits                          988     1052      +64     
- Misses                        138      159      +21     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@ruuushhh ruuushhh changed the base branch from exportable-expense-api to master November 29, 2023 14:54
Copy link

coderabbitai bot commented Nov 30, 2023

Walkthrough

The changes involve enhancing a Django application with new functionalities for handling Fyle platform data. New exception handling, task queuing, and import mechanisms are introduced, alongside modifications to models, views, and utilities to support synchronization with Fyle's expense management system. The updates include new URL patterns, improved error handling, and refactoring to streamline the integration process.

Changes

File Path Change Summary
apps/fyle/exceptions.py Introduced handle_exceptions decorator for exception handling.
apps/fyle/queue.py Added functions to queue import tasks for expenses.
apps/fyle/tasks.py Added import_expenses function for importing expenses from Fyle.
apps/fyle/views.py Added new view for syncing accounting exports and updated existing views.
apps/fyle/urls.py Added new URL pattern for accounting export synchronization.
apps/accounting_exports/models.py Updated models to handle expense grouping and accounting export creation.
apps/business_central/utils.py Corrected parameter name and updated data synchronization logic.
apps/fyle/helpers.py Added get_request function for HTTP GET requests and updated get_fyle_orgs.
apps/mappings/... Multiple changes across mapping modules to handle import logs, attribute synchronization, and error resolution.
apps/users/... Added helper functions and views for user profile and Fyle organizations retrieval.
apps/workspaces/... Refactored helpers, models, serializers, and views to support new onboarding states and workspace management.
tests/... Updated and added tests to cover new functionalities and changes.

"In the burrow of code, we hop and tweak,
With every commit, the future's not bleak.
Syncing expenses, handling fails,
A rabbit's work never derails. 🐇💻✨"


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • If you reply to a review comment from CodeRabbit, the bot will automatically respond.
  • To engage with CodeRabbit bot directly around the specific lines of code in the PR, mention @coderabbitai in your review comment
  • Note: Review comments are made on code diffs or files, not on the PR overview.
  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Note: For conversation with the bot, please use the review comments on code diffs or files.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

Copy link

Tests Skipped Failures Errors Time
19 0 💤 0 ❌ 0 🔥 2.232s ⏱️

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 7

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 7b2b4ab and b9f254b.
Files selected for processing (6)
  • apps/fyle/exceptions.py (1 hunks)
  • apps/fyle/queue.py (1 hunks)
  • apps/fyle/tasks.py (1 hunks)
  • apps/fyle/urls.py (2 hunks)
  • apps/fyle/views.py (2 hunks)
  • tests/test_fyle/test_views.py (1 hunks)
Additional comments: 7
apps/fyle/queue.py (2)
  • 18-24: The update_or_create method is used without error handling. Ensure that any potential exceptions are handled appropriately, possibly in the upstream code.

  • 27-30: The async_task function is called without error handling. Ensure that any potential exceptions are handled appropriately, possibly in the upstream code.

apps/fyle/tasks.py (2)
  • 21-66: The changes to the import_expenses function, including the addition of the accounting_export parameter and the use of the handle_exceptions decorator, are correctly implemented and follow best practices for error handling and database transactions. The logic for updating the last_synced_at timestamp and creating expense objects and accounting exports is sound. The function also correctly updates the accounting_export status to 'COMPLETE' at the end of the process.

  • 21-66: The summary does not mention the addition of the accounting_export parameter to the import_expenses function or the specific changes within the function. It would be beneficial to update the summary to reflect these important changes for clarity and completeness.

apps/fyle/urls.py (1)
  • 33-33: The new URL path for syncing accounting exports is correctly added. Ensure that the corresponding view AccoutingExportSyncView (after correcting the typo) is implemented with the necessary logic and error handling as described in the pull request summary.
apps/fyle/views.py (2)
  • 9-9: The import statements for queue_import_credit_card_expenses and queue_import_reimbursable_expenses have been correctly added.

  • 91-92: The post method correctly calls queue_import_reimbursable_expenses and queue_import_credit_card_expenses with synchronous=True.

Comment on lines 117 to 124
def test_accounting_export_sync_view(api_client, test_connection, create_temp_workspace, add_fyle_credentials, add_export_settings):

access_token = test_connection.access_token
url = reverse('sync-accounting-exports', kwargs={'workspace_id': 1})
api_client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(access_token))

response = api_client.post(url)
assert response.status_code == 200
Copy link

Choose a reason for hiding this comment

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

The test test_accounting_export_sync_view only checks for the response status code. It would be beneficial to also verify the expected side effects of the POST request, such as changes in the database or certain actions being triggered, to ensure the endpoint is functioning correctly beyond just returning a 200 status code.

Comment on lines +13 to +37
def handle_exceptions(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except FyleCredential.DoesNotExist:
logger.info('Fyle credentials not found %s', args[0]) # args[1] is workspace_id
args[1].detail = {'message': 'Fyle credentials do not exist in workspace'}
args[1].status = 'FAILED'
args[1].save()

except NoPrivilegeError:
logger.info('Invalid Fyle Credentials / Admin is disabled')
args[1].detail = {'message': 'Invalid Fyle Credentials / Admin is disabled'}
args[1].status = 'FAILED'
args[1].save()

except Exception:
error = traceback.format_exc()
args[1].detail = {'error': error}
args[1].status = 'FATAL'
args[1].save()
logger.exception('Something unexpected happened workspace_id: %s %s', args[0], args[1].detail)

return wrapper
Copy link

Choose a reason for hiding this comment

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

The handle_exceptions decorator assumes a specific order and type of arguments, which could lead to issues if the decorated function does not follow this pattern. Consider making the decorator more flexible to handle different argument orders or types, and ensure that exceptions are re-raised after logging and updating the status to notify the calling code of the exception.

+            raise

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.

Suggested change
def handle_exceptions(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except FyleCredential.DoesNotExist:
logger.info('Fyle credentials not found %s', args[0]) # args[1] is workspace_id
args[1].detail = {'message': 'Fyle credentials do not exist in workspace'}
args[1].status = 'FAILED'
args[1].save()
except NoPrivilegeError:
logger.info('Invalid Fyle Credentials / Admin is disabled')
args[1].detail = {'message': 'Invalid Fyle Credentials / Admin is disabled'}
args[1].status = 'FAILED'
args[1].save()
except Exception:
error = traceback.format_exc()
args[1].detail = {'error': error}
args[1].status = 'FATAL'
args[1].save()
logger.exception('Something unexpected happened workspace_id: %s %s', args[0], args[1].detail)
return wrapper
def handle_exceptions(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except FyleCredential.DoesNotExist:
logger.info('Fyle credentials not found %s', args[0]) # args[1] is workspace_id
args[1].detail = {'message': 'Fyle credentials do not exist in workspace'}
args[1].status = 'FAILED'
args[1].save()
raise
except NoPrivilegeError:
logger.info('Invalid Fyle Credentials / Admin is disabled')
args[1].detail = {'message': 'Invalid Fyle Credentials / Admin is disabled'}
args[1].status = 'FAILED'
args[1].save()
raise
except Exception:
error = traceback.format_exc()
args[1].detail = {'error': error}
args[1].status = 'FATAL'
args[1].save()
logger.exception('Something unexpected happened workspace_id: %s %s', args[0], args[1].detail)
raise
return wrapper

@@ -19,6 +19,7 @@
from django.urls import path

from apps.fyle.views import (
AccoutingExportSyncView,
Copy link

Choose a reason for hiding this comment

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

There appears to be a typo in the class name AccoutingExportSyncView. It should be corrected to AccountingExportSyncView if that is the intended name.

-    AccoutingExportSyncView,
+    AccountingExportSyncView,

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.

Suggested change
AccoutingExportSyncView,
AccountingExportSyncView,

@@ -76,3 +77,20 @@ def get(self, request, *args, **kwargs):
data={'exportable_expense_group_ids': exportable_ids},
status=status.HTTP_200_OK
)


class AccoutingExportSyncView(generics.CreateAPIView):
Copy link

Choose a reason for hiding this comment

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

The class name AccoutingExportSyncView contains a typo and should be corrected to AccountingExportSyncView.

- class AccoutingExportSyncView(generics.CreateAPIView):
+ class AccountingExportSyncView(generics.CreateAPIView):

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.

Suggested change
class AccoutingExportSyncView(generics.CreateAPIView):
class AccountingExportSyncView(generics.CreateAPIView):

Comment on lines +86 to +96
def post(self, request, *args, **kwargs):
"""
Post expense groups creation
"""

queue_import_reimbursable_expenses(kwargs['workspace_id'], synchronous=True)
queue_import_credit_card_expenses(kwargs['workspace_id'], synchronous=True)

return Response(
status=status.HTTP_200_OK
)
Copy link

Choose a reason for hiding this comment

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

The post method in AccoutingExportSyncView should use the handle_exceptions decorator to ensure consistent error handling across the application.

)
return

import_expenses(workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL')
Copy link

Choose a reason for hiding this comment

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

The import_expenses function is called with hardcoded values 'PERSONAL_CASH_ACCOUNT' and 'PERSONAL'. Consider making these values configurable or ensuring they are appropriate for all use cases.

Comment on lines 12 to 57
def queue_import_reimbursable_expenses(workspace_id: int, synchronous: bool = False):
"""
Queue Import of Reimbursable Expenses from Fyle
:param workspace_id: Workspace id
:return: None
"""
accounting_export, _ = AccountingExport.objects.update_or_create(
workspace_id=workspace_id,
type='FETCHING_REIMBURSABLE_EXPENSES',
defaults={
'status': 'IN_PROGRESS'
}
)

if not synchronous:
async_task(
'apps.fyle.tasks.import_reimbursable_expenses',
workspace_id, accounting_export,
)
return

import_expenses(workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL')


def queue_import_credit_card_expenses(workspace_id: int, synchronous: bool = False):
"""
Queue Import of Credit Card Expenses from Fyle
:param workspace_id: Workspace id
:return: None
"""
accounting_export, _ = AccountingExport.objects.update_or_create(
workspace_id=workspace_id,
type='FETCHING_CREDIT_CARD_EXPENSES',
defaults={
'status': 'IN_PROGRESS'
}
)

if not synchronous:
async_task(
'apps.fyle.tasks.import_credit_card_expenses',
workspace_id, accounting_export,
)
return

import_expenses(workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL')
Copy link

Choose a reason for hiding this comment

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

The functions queue_import_reimbursable_expenses and queue_import_credit_card_expenses have duplicated logic. Consider refactoring to reduce code duplication, perhaps by extracting common functionality into a helper function.

* Get Fyle Orgs API

* Get user profile api (#34)

* Bug Fix: After testing (#35)

* Get user profile api

* Bug Fix: After testing

* Bug Fix: After testing

* Onboarding state bug fix

* Onboarding state bug fix

* Test case resolved

* Import expenses fixed

* Import expenses bug fixed

* test cases resolved

* test cases

* logic fixed for date

* Fix tests

* logic fixed for date

* Imports Folder Structure (#36)

* Imports Folder Structure

* Import base files added (#39)

* Import base files added

* Import Categories support added (#40)

* Import Categories support added

* Import merchants support added (#41)

* Import merchants support added

* Import cost centers support added (#42)

* Import cost centers support added

* Import project support added (#43)

* Import project support added

* Schedule, queues signals configured (#44)

* Schedule, queues signals configured

* Imports bugs fixed (#45)

* Imports bugs fixed

* Comments resolved
Copy link

Tests Skipped Failures Errors Time
21 0 💤 0 ❌ 0 🔥 2.430s ⏱️

@ruuushhh ruuushhh merged commit d25469e into master Nov 30, 2023
1 check passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 30

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between b9f254b and 42a62a4.
Files selected for processing (37)
  • apps/accounting_exports/models.py (3 hunks)
  • apps/business_central/utils.py (6 hunks)
  • apps/fyle/helpers.py (2 hunks)
  • apps/fyle/queue.py (1 hunks)
  • apps/fyle/serializers.py (2 hunks)
  • apps/fyle/tasks.py (1 hunks)
  • apps/fyle/views.py (3 hunks)
  • apps/mappings/apps.py (1 hunks)
  • apps/mappings/constants.py (1 hunks)
  • apps/mappings/exceptions.py (1 hunks)
  • apps/mappings/imports/modules/base.py (1 hunks)
  • apps/mappings/imports/modules/categories.py (1 hunks)
  • apps/mappings/imports/modules/cost_centers.py (1 hunks)
  • apps/mappings/imports/modules/expense_custom_fields.py (1 hunks)
  • apps/mappings/imports/modules/merchants.py (1 hunks)
  • apps/mappings/imports/modules/projects.py (1 hunks)
  • apps/mappings/imports/queues.py (1 hunks)
  • apps/mappings/imports/schedules.py (1 hunks)
  • apps/mappings/imports/tasks.py (1 hunks)
  • apps/mappings/models.py (1 hunks)
  • apps/mappings/signals.py (1 hunks)
  • apps/mappings/tasks.py (1 hunks)
  • apps/mappings/urls.py (1 hunks)
  • apps/users/helpers.py (1 hunks)
  • apps/users/urls.py (1 hunks)
  • apps/users/views.py (1 hunks)
  • apps/workspaces/helpers.py (2 hunks)
  • apps/workspaces/models.py (3 hunks)
  • apps/workspaces/serializers.py (3 hunks)
  • apps/workspaces/urls.py (2 hunks)
  • apps/workspaces/views.py (2 hunks)
  • ms_business_central_api/urls.py (1 hunks)
  • tests/conftest.py (2 hunks)
  • tests/test_fyle/fixtures.py (2 hunks)
  • tests/test_fyle/test_views.py (3 hunks)
  • tests/test_users/test_views.py (1 hunks)
  • tests/test_workspaces/test_view.py (2 hunks)
Files skipped from review due to trivial changes (4)
  • apps/mappings/constants.py
  • apps/mappings/signals.py
  • apps/mappings/urls.py
  • apps/workspaces/urls.py
Additional comments: 53
apps/accounting_exports/models.py (1)
  • 37-83: The logic in _group_expenses function seems to be correctly implemented. It groups expenses based on the fund_source and fields specified in export_setting. The function also dynamically adjusts the fields used for grouping based on the fund_source. This is a good use of dynamic programming to handle different grouping requirements.
apps/business_central/utils.py (5)
  • 17-23: The parameter name correction from enviroment to environment aligns with the summary and is a good fix for a typo.

  • 91-97: The addition of the field_names parameter in the sync_accounts method is consistent with the summary and is a good enhancement for flexibility.

  • 103-112: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [100-109]

The addition of the field_names parameter in the sync_vendors method is consistent with the summary and is a good enhancement for flexibility.

  • 115-124: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [112-121]

The addition of the field_names parameter in the sync_employees method is consistent with the summary and is a good enhancement for flexibility.

  • 127-134: > Note: This review was outside the patches, so it was mapped to the patch with the greatest overlap. Original lines [124-133]

The addition of the field_names parameter in the sync_locations method is consistent with the summary and is a good enhancement for flexibility.

apps/fyle/helpers.py (1)
  • 141-147: Ensure that no default parameters are needed for the get_request call within the get_fyle_orgs function.
apps/fyle/serializers.py (3)
  • 8-8: The summary states that the import for ExpenseAttribute was both added and removed, which is contradictory. The hunk shows that the import is present, so the summary is incorrect.

  • 112-126: The changes to ExpenseFieldSerializer align with the summary, introducing new fields field_name, type, and is_custom, and updating the get_expense_fields method to accept workspace_id as an integer parameter.

  • 119-119: The method get_expense_fields has been correctly updated to use workspace_id as a parameter instead of validated_data.

apps/fyle/views.py (6)
  • 9-9: The new imports for queue_import_credit_card_expenses and queue_import_reimbursable_expenses are correctly added.

  • 66-67: The get_queryset method is correctly added to CustomFieldView to customize the queryset.

  • 84-84: The class name AccoutingExportSyncView contains a typo and should be corrected to AccountingExportSyncView.

- class AccoutingExportSyncView(generics.CreateAPIView):
+ class AccountingExportSyncView(generics.CreateAPIView):
  • 88-98: The post method in AccoutingExportSyncView should use the handle_exceptions decorator to ensure consistent error handling across the application.

  • 64-64: The pagination_class is correctly set to None for CustomFieldView.

  • 61-67: The queryset attribute is correctly removed from CustomFieldView.

apps/mappings/apps.py (1)
  • 8-10: The implementation of the ready method in MappingsConfig class follows the Django convention for importing signals. Ensure that the signals are defined correctly in apps.mappings.signals to avoid any issues with signal registration.
apps/mappings/exceptions.py (1)
  • 16-65: The handle_import_exceptions decorator is well-structured and covers a range of exceptions, logging them appropriately. Ensure that all functions using this decorator are compatible with its signature and error handling strategy.
apps/mappings/imports/modules/categories.py (1)
  • 14-85: The implementation of the Category class methods appears to be correct and follows the expected logic for importing categories and creating mappings. The use of type hints and clear method names enhances readability and maintainability.
apps/mappings/imports/modules/cost_centers.py (3)
  • 14-21: The constructor of the CostCenter class correctly sets up the necessary fields for the base class. Ensure that the destination_field and sync_after parameters are always provided when creating an instance of CostCenter.

  • 23-27: The trigger_import method correctly calls check_import_log_and_start_import. Verify that this method is implemented in the Base class and does not require any parameters.

  • 29-59: The construct_fyle_payload method logic appears to be correct. However, verify that the default behavior for is_enabled should be True when attribute.active is None. Also, confirm that existing_fyle_attributes_map contains lowercased cost center names for the comparison in line 56 to work as intended.

apps/mappings/imports/modules/merchants.py (3)
  • 33-37: The is_auto_sync_status_allowed parameter is still present in the construct_fyle_payload method signature, which contradicts the summary stating it should be removed.
-    def construct_fyle_payload(
-        self,
-        paginated_destination_attributes: List[DestinationAttribute],
-        existing_fyle_attributes_map: object,
-        is_auto_sync_status_allowed: bool
-    ):
  • 56-71: The use of the handle_import_exceptions decorator aligns with the summary's mention of improved error handling.

  • 65-71: The sync_expense_attributes method is called twice in import_destination_attribute_to_fyle. Verify if this is intentional or an oversight, as it could lead to redundant operations.

-        self.sync_expense_attributes(platform)
-        self.sync_expense_attributes(platform)
apps/mappings/imports/schedules.py (1)
  • 28-28: Verify if the next_run should be set to datetime.now() which schedules the task to run immediately. Confirm if this is the intended behavior or if it should be scheduled for a later time.
apps/users/urls.py (1)
  • 18-22: The URL patterns for UserProfileView and FyleOrgsView have been added correctly and follow Django's URL configuration conventions.
apps/users/views.py (2)
  • 9-26: The implementation of FyleOrgsView and its get method aligns with the summary and follows best practices for Django REST framework views. Ensure that the helpers used within the get method have proper error handling and logging mechanisms in place.

  • 29-41: The implementation of UserProfileView and its get method aligns with the summary and follows best practices for Django REST framework views. Ensure that the helper get_user_profile used within the get method has proper error handling and logging mechanisms in place.

apps/workspaces/helpers.py (2)
  • 1-8: The reordering of import statements is a cosmetic change and does not affect functionality. It's good practice to follow PEP 8 guidelines for imports ordering.

  • 121-124: The change in onboarding_state from "COMPANY_SELECTION" to "EXPORT_SETTINGS" should be verified across the application to ensure it does not negatively impact other functionalities.

apps/workspaces/models.py (4)
  • 5-15: The summary mentions the addition of imports for BooleanFalseField and BooleanTrueField, but these imports are not visible in the provided hunk. Please verify if the imports are correctly placed in the file.

  • 21-21: The addition of 'COMPANY_SELECTION', 'COMPANY_SELECTION' to ONBOARDING_STATE_CHOICES is correct and aligns with the summary.

  • 29-30: The change in the default return value of get_default_onboarding_state to 'CONNECTION' is correct and aligns with the summary.

  • 41-42: The renaming of fields reimbursable_last_synced_at and credit_card_last_synced_at is correct and aligns with the summary.

apps/workspaces/serializers.py (4)
  • 3-3: The summary indicates that the import statement for rest_framework.serializers was removed, but it is still present in the code.

  • 21-21: The summary indicates that the import statement for ms_business_central_api.utils.assert_valid was removed, but it is still present in the code.

  • 12-12: The summary does not mention the addition of connect_business_central import, but it is present in the code.

  • 210-214: The summary does not mention changes to WorkspaceAdminSerializer, but the hunk shows the addition of email and name fields.

apps/workspaces/views.py (2)
  • 3-17: The import statements for get_user_model, WorkspaceSerializer, and assert_valid should be removed according to the summary, but they are still present in the hunk. Please verify if they are indeed to be removed or if the summary is incorrect.
- from django.contrib.auth import get_user_model
- from apps.workspaces.serializers import WorkspaceSerializer
- from ms_business_central_api.utils import assert_valid
  • 116-122: The addition of pagination_class = None and the new get_queryset method in WorkspaceAdminsView class are consistent with the summary and seem to be correctly implemented.
ms_business_central_api/urls.py (2)
  • 20-22: The summary mentions new URL patterns added at lines 7 and 9, but the hunk shows changes at lines 20 and 22. Please verify the line numbers and ensure the summary accurately reflects the changes made in the code.

  • 20-22: The addition of URL patterns for 'api/auth/' and 'api/user/' is consistent with the summary and follows Django's URL configuration conventions.

tests/conftest.py (2)
  • 115-121: The field names reimbursable_last_synced_at and credit_card_last_synced_at have been correctly updated in the create_temp_workspace fixture.

  • 307-311: The logic to set reimbursable_expense_date based on the workspace_id has been correctly implemented in the add_export_settings fixture.

tests/test_fyle/fixtures.py (4)
  • 27-45: The addition of the 'get_all_orgs' key with organization details is consistent with the summary.

  • 224-284: The addition of the 'expenses' key with expense details is consistent with the summary.

  • 27-45: Ensure that the new keys and their respective data structures are properly integrated and used in the tests that consume this fixture data.

  • 224-284: Ensure that the new keys and their respective data structures are properly integrated and used in the tests that consume this fixture data.

tests/test_fyle/test_views.py (2)
  • 3-10: The imports for AccountingExport and Expense are correctly added to support the new test function test_accounting_export_sync_view.

  • 119-139: The new test function test_accounting_export_sync_view correctly tests the synchronization of accounting exports, asserts the response status code, and checks the AccountingExport status and Expense count as described in the summary.

tests/test_users/test_views.py (1)
  • 12-28: The new test function test_get_all_orgs_view correctly mocks the get_fyle_orgs function, sets up the API client with the necessary credentials, and asserts both the status code and the response data. This aligns with the summary provided.
tests/test_workspaces/test_view.py (1)
  • 19-22: The addition of the assertion for 'onboarding_state' is a good practice to ensure the integrity of the response data.

Comment on lines +13 to +22
sync_functions = {
'ACCOUNT': business_central_connection.sync_accounts,
'COMPANY': business_central_connection.sync_companies,
'LOCATION': business_central_connection.sync_locations,
'EMPLOYEE': business_central_connection.sync_employees,
'VENDOR': business_central_connection.sync_vendors,
}

sync_function = sync_functions[business_central_attribute_type]
sync_function()
Copy link

Choose a reason for hiding this comment

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

The function lacks error handling for an invalid business_central_attribute_type. Consider adding a try-except block to handle KeyError and provide a meaningful error message if an unsupported attribute type is passed.

+ try:
    sync_function = sync_functions[business_central_attribute_type]
    sync_function()
+ except KeyError:
+     # Handle the error or log a message

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.

Suggested change
sync_functions = {
'ACCOUNT': business_central_connection.sync_accounts,
'COMPANY': business_central_connection.sync_companies,
'LOCATION': business_central_connection.sync_locations,
'EMPLOYEE': business_central_connection.sync_employees,
'VENDOR': business_central_connection.sync_vendors,
}
sync_function = sync_functions[business_central_attribute_type]
sync_function()
try:
sync_function = sync_functions[business_central_attribute_type]
sync_function()
except KeyError:
# Handle the error or log a message

Comment on lines +31 to +41
def test_get_user_profile(api_client, test_connection, create_temp_workspace, add_fyle_credentials):
"""
Test get user profile
"""
access_token = test_connection.access_token
url = reverse('user-profile')

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

response = api_client.get(url)
assert response.status_code == 200
Copy link

Choose a reason for hiding this comment

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

The new test function test_get_user_profile sets up the API client with the necessary credentials and asserts the status code of the response. However, it does not assert the response data. If the response data is important to the test, consider adding an assertion for it.

@@ -23,6 +19,7 @@ def test_post_of_workspace(api_client, test_connection):
assert response.status_code == 201
assert workspace.name == response.data['name']
assert workspace.org_id == response.data['org_id']
assert workspace.onboarding_state == response.data['onboarding_state']

response = json.loads(response.content)
Copy link

Choose a reason for hiding this comment

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

The response should be converted to JSON before making assertions on response.data.

-    response = json.loads(response.content)
+    response_data = json.loads(response.content)

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.

Suggested change
response = json.loads(response.content)
response_data = json.loads(response.content)

"""
mapping_settings = MappingSetting.objects.filter(workspace_id=workspace_id, import_to_fyle=True)
custom_field_mapping_settings = MappingSetting.objects.filter(workspace_id=workspace_id, is_custom=True, import_to_fyle=True)
import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
Copy link

Choose a reason for hiding this comment

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

Consider adding error handling for the case where ImportSetting does not exist for the given workspace_id. This will prevent the function from raising an unhandled ImportSetting.DoesNotExist exception.

-    import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
+    try:
+        import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
+    except ImportSetting.DoesNotExist:
+        # Handle the error or log it
+        return

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.

Suggested change
import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
try:
import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
except ImportSetting.DoesNotExist:
# Handle the error or log it
return

item = ExpenseCustomField(workspace_id, source_field, destination_field, sync_after)
item.trigger_import()
else:
module_class = SOURCE_FIELD_CLASS_MAP[source_field]
Copy link

Choose a reason for hiding this comment

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

Consider adding error handling for the case where source_field is not found in SOURCE_FIELD_CLASS_MAP to prevent a KeyError.

Comment on lines +30 to +41
refresh_token = AuthToken.objects.get(user__user_id=request.user).refresh_token
cluster_domain, _ = get_cluster_domain_and_refresh_token(request.user)

fyle_credentials = FyleCredential(
cluster_domain=cluster_domain,
refresh_token=refresh_token
)

platform = PlatformConnector(fyle_credentials)
employee_profile = platform.connection.v1beta.spender.my_profile.get()

return employee_profile
Copy link

Choose a reason for hiding this comment

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

Ensure that the AuthToken.DoesNotExist exception is handled when fetching the refresh token. This is important for robust error handling and to prevent the function from failing unexpectedly.

-    refresh_token = AuthToken.objects.get(user__user_id=request.user).refresh_token
+    try:
+        refresh_token = AuthToken.objects.get(user__user_id=request.user).refresh_token
+    except AuthToken.DoesNotExist:
+        # Handle the exception, e.g., by logging and returning an appropriate response

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.

Suggested change
refresh_token = AuthToken.objects.get(user__user_id=request.user).refresh_token
cluster_domain, _ = get_cluster_domain_and_refresh_token(request.user)
fyle_credentials = FyleCredential(
cluster_domain=cluster_domain,
refresh_token=refresh_token
)
platform = PlatformConnector(fyle_credentials)
employee_profile = platform.connection.v1beta.spender.my_profile.get()
return employee_profile
try:
refresh_token = AuthToken.objects.get(user__user_id=request.user).refresh_token
except AuthToken.DoesNotExist:
# Handle the exception, e.g., by logging and returning an appropriate response
cluster_domain, _ = get_cluster_domain_and_refresh_token(request.user)
fyle_credentials = FyleCredential(
cluster_domain=cluster_domain,
refresh_token=refresh_token
)
platform = PlatformConnector(fyle_credentials)
employee_profile = platform.connection.v1beta.spender.my_profile.get()
return employee_profile

Comment on lines +105 to 147
@staticmethod
def create_accounting_export(expense_objects: List[Expense], fund_source: str, workspace_id):
"""
Group expenses by report_id and fund_source, format date fields, and create AccountingExport objects.
"""
# Retrieve the ExportSetting for the workspace
export_setting = ExportSetting.objects.get(workspace_id=workspace_id)

# Group expenses based on specified fields and fund_source
accounting_exports = _group_expenses(expense_objects, export_setting, fund_source)

fund_source_map = {
'PERSONAL': 'reimbursable',
'CCC': 'credit_card'
}

for accounting_export in accounting_exports:
# Determine the date field based on fund_source
date_field = getattr(export_setting, f"{fund_source_map.get(fund_source)}_expense_date", None)

# Calculate and assign 'last_spent_at' based on the chosen date field
if date_field == 'last_spent_at':
latest_expense = Expense.objects.filter(id__in=accounting_export['expense_ids']).order_by('-spent_at').first()
accounting_export['last_spent_at'] = latest_expense.spent_at if latest_expense else None

# Store expense IDs and remove unnecessary keys
expense_ids = accounting_export['expense_ids']
accounting_export[date_field] = accounting_export[date_field].strftime('%Y-%m-%dT%H:%M:%S')
accounting_export.pop('total')
accounting_export.pop('expense_ids')

# Create an AccountingExport object for the expense group
accounting_export_instance = AccountingExport.objects.create(
workspace_id=workspace_id,
fund_source=accounting_export['fund_source'],
description=accounting_export,
status='EXPORT_READY'
)

# Add related expenses to the AccountingExport object
accounting_export_instance.expenses.add(*expense_ids)


Copy link

Choose a reason for hiding this comment

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

In the create_accounting_export function, there is a potential issue with the date formatting logic. If the date_field is not 'last_spent_at', the code attempts to format the date without checking if the date is present or not. This could lead to an AttributeError if accounting_export[date_field] is None.

- accounting_export[date_field] = accounting_export[date_field].strftime('%Y-%m-%dT%H:%M:%S')
+ if accounting_export.get(date_field):
+     accounting_export[date_field] = accounting_export[date_field].strftime('%Y-%m-%dT%H:%M:%S')

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.

Suggested change
@staticmethod
def create_accounting_export(expense_objects: List[Expense], fund_source: str, workspace_id):
"""
Group expenses by report_id and fund_source, format date fields, and create AccountingExport objects.
"""
# Retrieve the ExportSetting for the workspace
export_setting = ExportSetting.objects.get(workspace_id=workspace_id)
# Group expenses based on specified fields and fund_source
accounting_exports = _group_expenses(expense_objects, export_setting, fund_source)
fund_source_map = {
'PERSONAL': 'reimbursable',
'CCC': 'credit_card'
}
for accounting_export in accounting_exports:
# Determine the date field based on fund_source
date_field = getattr(export_setting, f"{fund_source_map.get(fund_source)}_expense_date", None)
# Calculate and assign 'last_spent_at' based on the chosen date field
if date_field == 'last_spent_at':
latest_expense = Expense.objects.filter(id__in=accounting_export['expense_ids']).order_by('-spent_at').first()
accounting_export['last_spent_at'] = latest_expense.spent_at if latest_expense else None
# Store expense IDs and remove unnecessary keys
expense_ids = accounting_export['expense_ids']
accounting_export[date_field] = accounting_export[date_field].strftime('%Y-%m-%dT%H:%M:%S')
accounting_export.pop('total')
accounting_export.pop('expense_ids')
# Create an AccountingExport object for the expense group
accounting_export_instance = AccountingExport.objects.create(
workspace_id=workspace_id,
fund_source=accounting_export['fund_source'],
description=accounting_export,
status='EXPORT_READY'
)
# Add related expenses to the AccountingExport object
accounting_export_instance.expenses.add(*expense_ids)
@staticmethod
def create_accounting_export(expense_objects: List[Expense], fund_source: str, workspace_id):
"""
Group expenses by report_id and fund_source, format date fields, and create AccountingExport objects.
"""
# Retrieve the ExportSetting for the workspace
export_setting = ExportSetting.objects.get(workspace_id=workspace_id)
# Group expenses based on specified fields and fund_source
accounting_exports = _group_expenses(expense_objects, export_setting, fund_source)
fund_source_map = {
'PERSONAL': 'reimbursable',
'CCC': 'credit_card'
}
for accounting_export in accounting_exports:
# Determine the date field based on fund_source
date_field = getattr(export_setting, f"{fund_source_map.get(fund_source)}_expense_date", None)
# Calculate and assign 'last_spent_at' based on the chosen date field
if date_field == 'last_spent_at':
latest_expense = Expense.objects.filter(id__in=accounting_export['expense_ids']).order_by('-spent_at').first()
accounting_export['last_spent_at'] = latest_expense.spent_at if latest_expense else None
# Store expense IDs and remove unnecessary keys
expense_ids = accounting_export['expense_ids']
if accounting_export.get(date_field):
accounting_export[date_field] = accounting_export[date_field].strftime('%Y-%m-%dT%H:%M:%S')
accounting_export.pop('total')
accounting_export.pop('expense_ids')
# Create an AccountingExport object for the expense group
accounting_export_instance = AccountingExport.objects.create(
workspace_id=workspace_id,
fund_source=accounting_export['fund_source'],
description=accounting_export,
status='EXPORT_READY'
)
# Add related expenses to the AccountingExport object
accounting_export_instance.expenses.add(*expense_ids)


destination_attributes_count = DestinationAttribute.objects.filter(**filters).count()

is_auto_sync_status_allowed = self.get_auto_sync_permission()
Copy link

Choose a reason for hiding this comment

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

The call to get_auto_sync_permission is redundant as it is already called and stored in is_auto_sync_status_allowed earlier in the construct_payload_and_import_to_fyle method.

- is_auto_sync_status_allowed = self.get_auto_sync_permission()

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.

Suggested change
is_auto_sync_status_allowed = self.get_auto_sync_permission()

:param import_log: ImportLog object
"""
if is_last_batch:
import_log.last_successful_run_at = datetime.now()
Copy link

Choose a reason for hiding this comment

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

The datetime.now() method is used without timezone awareness. It is recommended to use datetime.now(timezone.utc) to ensure that the time is stored in a consistent and timezone-aware format.

- import_log.last_successful_run_at = datetime.now()
+ import_log.last_successful_run_at = datetime.now(timezone.utc)

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.

Suggested change
import_log.last_successful_run_at = datetime.now()
import_log.last_successful_run_at = datetime.now(timezone.utc)

'status': 'IN_PROGRESS'
}
)
time_difference = datetime.now() - timedelta(minutes=30)
Copy link

Choose a reason for hiding this comment

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

The datetime.now() method is used without timezone awareness. It is recommended to use datetime.now(timezone.utc) to ensure that the time is stored in a consistent and timezone-aware format.

- time_difference = datetime.now() - timedelta(minutes=30)
+ time_difference = datetime.now(timezone.utc) - timedelta(minutes=30)

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.

Suggested change
time_difference = datetime.now() - timedelta(minutes=30)
time_difference = datetime.now(timezone.utc) - timedelta(minutes=30)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants