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

Base Export Structure added #50

Closed
wants to merge 2 commits into from
Closed

Conversation

ruuushhh
Copy link
Contributor

@ruuushhh ruuushhh commented Dec 15, 2023

Summary by CodeRabbit

  • New Features

    • Introduced a new function to update the last export details for workspaces.
    • Added new export mode options 'MANUAL' and 'AUTO' for workspace settings.
    • Implemented a new model to track the last export details, including time and mode.
  • Bug Fixes

    • Enhanced error handling with new functions and improved a decorator to manage specific business central exceptions.
  • Documentation

    • No visible changes to end-user documentation.
  • Refactor, Style, Tests, Chores, Revert

    • No user-impacting changes in these categories.

@ruuushhh ruuushhh self-assigned this Dec 15, 2023
Copy link

Tests Skipped Failures Errors Time
47 0 💤 0 ❌ 0 🔥 4.355s ⏱️

Copy link

Tests Skipped Failures Errors Time
47 0 💤 0 ❌ 0 🔥 4.384s ⏱️

Copy link

coderabbitai bot commented Dec 15, 2023

Walkthrough

The recent updates include the introduction of a new function to track export details, improved error handling in the Business Central integration, expansion of export mode options, and the creation of a new model to store export details. These changes enhance the application's ability to manage and log export activities, as well as handle exceptions more effectively.

Changes

File Path Change Summary
.../business_central/actions.py Added update_last_export_details function for updating export counts.
.../business_central/exceptions.py Enhanced exception handling with new functions and updated decorator logic.
.../workspaces/models.py Introduced EXPORT_MODE_CHOICES and new LastExportDetail model.
.../workspaces/serializers.py Imported LastExportDetail and modified create method to instantiate it.
.../ms_business_central_api/exceptions.py Added new BulkError exception class.

🐰✨
In the land of code, where the data bytes hop,
We've added new tricks to our workshop's shop.
With modes AUTO and MANUAL, we choose our export play,
And handle our errors in a much smarter way.
🥕📈

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on X ?


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • You can reply to a review comment made by CodeRabbit.
  • You can tag CodeRabbit on specific lines of code or files in the PR by tagging @coderabbitai in a comment.
  • You can tag @coderabbitai in a PR comment and ask one-off questions about the PR and the codebase. Use quoted replies to pass the context for follow-up questions.

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.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

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
47 0 💤 0 ❌ 0 🔥 3.982s ⏱️

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: 4

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 14b6729 and 0652db4.
Files selected for processing (5)
  • apps/business_central/actions.py (1 hunks)
  • apps/business_central/exceptions.py (1 hunks)
  • apps/workspaces/models.py (2 hunks)
  • apps/workspaces/serializers.py (2 hunks)
  • ms_business_central_api/exceptions.py (1 hunks)
Files skipped from review due to trivial changes (1)
  • ms_business_central_api/exceptions.py
Additional comments: 11
apps/business_central/actions.py (2)
  • 17-19: The calculation of total_accounting_exports_count by summing failed_accounting_exports_count and successful_accounting_exports_count assumes that there are no other export statuses to consider. If there are other statuses that should be included in the total count, this calculation may need to be adjusted.

  • 22-22: Returning the updated last_export_detail object is a good practice as it allows the caller to use the updated details immediately if needed.

apps/business_central/exceptions.py (3)
  • 16-27:
    The handle_business_central_error function correctly logs the exception response, updates the Error model with the error details, and sets the accounting_export status to 'FAILED'. This aligns with the PR objectives to improve exception handling.

  • 30-76:
    The handle_business_central_exceptions decorator function provides comprehensive error handling for different types of exceptions, ensuring that the accounting_export status and details are updated accordingly. The inclusion of update_last_export_details at the end of the try-except block ensures that export details are updated in all cases.

  • 64-70:
    The catch-all for generic Exception is appropriate here as it's used to set the accounting_export status to 'FATAL' and log the error, which is suitable for unexpected errors that are not explicitly handled by the other except blocks.

apps/workspaces/models.py (3)
  • 133-136: The EXPORT_MODE_CHOICES tuple is correctly defined following Django's convention for choices. This will be used in the LastExportDetail model's export_mode field.

  • 139-140: The BaseModel is correctly used as an abstract base class for the LastExportDetail model, ensuring that the created_at and updated_at fields are consistently managed across models.

  • 249-250: The db_table name for LastExportDetail follows the established naming convention and clearly describes the table's purpose.

apps/workspaces/serializers.py (3)
  • 19-19: The import of LastExportDetail is correctly placed and follows the alphabetical order of imports.

  • 67-67: Verify that the LastExportDetail model's workspace_id field is sufficient for creating a new instance and that no other mandatory fields are required. If additional fields are necessary, they should be included in the creation call.

  • 64-71: Ensure that the user field is correctly handled when adding a user to the workspace. It might be necessary to verify that the User.objects.get(user_id=user) call returns the expected user object and that the user_id field matches the user variable obtained from the request context.

Comment on lines +7 to +22
def update_last_export_details(workspace_id):
last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id)

failed_exports = AccountingExport.objects.filter(~Q(type__in=['FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_CREDIT_CARD_EXPENSES']), workspace_id=workspace_id, status__in=['FAILED', 'FATAL']).count()

successful_exports = AccountingExport.objects.filter(
~Q(type__in=['FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_CREDIT_CARD_EXPENSES']),
workspace_id=workspace_id, status='COMPLETE',
).count()

last_export_detail.failed_accounting_exports_count = failed_exports
last_export_detail.successful_accounting_exports_count = successful_exports
last_export_detail.total_accounting_exports_count = failed_exports + successful_exports
last_export_detail.save()

return last_export_detail
Copy link

Choose a reason for hiding this comment

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

The function update_last_export_details uses LastExportDetail.objects.get(workspace_id=workspace_id) without handling the potential DoesNotExist exception that is raised if no LastExportDetail instance is found for the given workspace_id. It's important to handle this case to avoid unhandled exceptions which could disrupt the flow of the application.

 def update_last_export_details(workspace_id):
+    try:
         last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id)
+    except LastExportDetail.DoesNotExist:
+        # Handle the exception, e.g., create a new LastExportDetail instance or log an error
+        pass

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 update_last_export_details(workspace_id):
last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id)
failed_exports = AccountingExport.objects.filter(~Q(type__in=['FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_CREDIT_CARD_EXPENSES']), workspace_id=workspace_id, status__in=['FAILED', 'FATAL']).count()
successful_exports = AccountingExport.objects.filter(
~Q(type__in=['FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_CREDIT_CARD_EXPENSES']),
workspace_id=workspace_id, status='COMPLETE',
).count()
last_export_detail.failed_accounting_exports_count = failed_exports
last_export_detail.successful_accounting_exports_count = successful_exports
last_export_detail.total_accounting_exports_count = failed_exports + successful_exports
last_export_detail.save()
return last_export_detail
def update_last_export_details(workspace_id):
try:
last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id)
except LastExportDetail.DoesNotExist:
# Handle the exception, e.g., create a new LastExportDetail instance or log an error
pass
failed_exports = AccountingExport.objects.filter(~Q(type__in=['FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_CREDIT_CARD_EXPENSES']), workspace_id=workspace_id, status__in=['FAILED', 'FATAL']).count()
successful_exports = AccountingExport.objects.filter(
~Q(type__in=['FETCHING_REIMBURSABLE_EXPENSES', 'FETCHING_CREDIT_CARD_EXPENSES']),
workspace_id=workspace_id, status='COMPLETE',
).count()
last_export_detail.failed_accounting_exports_count = failed_exports
last_export_detail.successful_accounting_exports_count = successful_exports
last_export_detail.total_accounting_exports_count = failed_exports + successful_exports
last_export_detail.save()
return last_export_detail

def decorator(func):
def new_fn(*args):

accounting_export = args[0]
Copy link

Choose a reason for hiding this comment

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

Consider using keyword arguments or explicit argument names to access accounting_export within the decorator to avoid potential errors if the function signature changes in the future.

Comment on lines +36 to +72
try:
return func(*args)
except (FyleCredential.DoesNotExist):
logger.info('Fyle credentials not found %s', accounting_export.workspace_id)
accounting_export.detail = {'message': 'Fyle credentials do not exist in workspace'}
accounting_export.status = 'FAILED'

accounting_export.save()

except BusinessCentralCredentials.DoesNotExist:
logger.info('Business Central Account not connected / token expired for workspace_id %s / accounting export %s', accounting_export.workspace_id, accounting_export.id)
detail = {'accounting_export_id': accounting_export.id, 'message': 'Business Central Account not connected / token expired'}
accounting_export.status = 'FAILED'
accounting_export.detail = detail

accounting_export.save()

except WrongParamsError as exception:
handle_business_central_error(exception, accounting_export, 'Purchase Invoice')

except BulkError as exception:
logger.info(exception.response)
detail = exception.response
accounting_export.status = 'FAILED'
accounting_export.detail = detail

accounting_export.save()

except Exception as error:
error = traceback.format_exc()
accounting_export.detail = {'error': error}
accounting_export.status = 'FATAL'

accounting_export.save()
logger.error('Something unexpected happened workspace_id: %s %s', accounting_export.workspace_id, accounting_export.detail)

update_last_export_details(accounting_export.workspace_id)
Copy link

Choose a reason for hiding this comment

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

There is repeated code for updating accounting_export status and saving it within each except block. Consider refactoring to a common function or using a finally block to reduce duplication and improve maintainability.

Comment on lines +237 to +250
class LastExportDetail(BaseModel):
"""
Table to store Last Export Details
"""

id = models.AutoField(primary_key=True)
last_exported_at = models.DateTimeField(help_text='Last exported at datetime', null=True)
export_mode = models.CharField(max_length=50, help_text='Mode of the export Auto / Manual', choices=EXPORT_MODE_CHOICES, null=True)
total_accounting_exports_count = models.IntegerField(help_text='Total count of accounting exports exported', null=True)
successful_accounting_exports_count = models.IntegerField(help_text='count of successful accounting_exports ', null=True)
failed_accounting_exports_count = models.IntegerField(help_text='count of failed accounting_exports ', null=True)

class Meta:
db_table = 'last_export_details'
Copy link

Choose a reason for hiding this comment

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

The LastExportDetail model is well-defined with appropriate field types, help texts, and nullability. However, consider adding indexes to frequently queried fields like last_exported_at and export_mode to improve database performance.

class LastExportDetail(BaseModel):
    ...
    last_exported_at = models.DateTimeField(help_text='Last exported at datetime', null=True, db_index=True)
    export_mode = models.CharField(max_length=50, help_text='Mode of the export Auto / Manual', choices=EXPORT_MODE_CHOICES, null=True, db_index=True)
    ...

@ruuushhh ruuushhh closed this Jan 17, 2024
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.

1 participant