-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'fyle_import_attributes' into accounting_export_apis
- Loading branch information
Showing
31 changed files
with
751 additions
and
144 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
name: Continuous Integration | ||
|
||
on: | ||
pull_request: | ||
types: [assigned, opened, synchronize, reopened] | ||
|
||
jobs: | ||
pytest: | ||
runs-on: ubuntu-latest | ||
environment: CI Environment | ||
steps: | ||
- uses: actions/checkout@v2 | ||
- name: Bring up Services and Run Tests | ||
run: | | ||
docker-compose -f docker-compose-pipeline.yml build | ||
docker-compose -f docker-compose-pipeline.yml up -d | ||
docker-compose -f docker-compose-pipeline.yml exec -T api pytest tests/ --cov --junit-xml=test-reports/report.xml --cov-report=xml --cov-fail-under=70 | ||
echo "STATUS=$(cat pytest-coverage.txt | grep 'Required test' | awk '{ print $1 }')" >> $GITHUB_ENV | ||
echo "FAILED=$(cat test-reports/report.xml | awk -F'=' '{print $5}' | awk -F' ' '{gsub(/"/, "", $1); print $1}')" >> $GITHUB_ENV | ||
- name: Upload coverage reports to Codecov with GitHub Action | ||
uses: codecov/codecov-action@v3 | ||
- name: Pytest coverage comment | ||
uses: MishaKav/pytest-coverage-comment@main | ||
if: ${{ always() && github.ref != 'refs/heads/master' }} | ||
with: | ||
create-new-comment: true | ||
junitxml-path: ./test-reports/report.xml | ||
- name: Evaluate Coverage | ||
if: ${{ (env.STATUS == 'FAIL') || (env.FAILED > 0) }} | ||
run: exit 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
DEFAULT_FYLE_CONDITIONS = [ | ||
{ | ||
'field_name': 'employee_email', | ||
'type': 'SELECT', | ||
'is_custom': False | ||
}, | ||
{ | ||
'field_name': 'claim_number', | ||
'type': 'TEXT', | ||
'is_custom': False | ||
}, | ||
{ | ||
'field_name': 'report_title', | ||
'type': 'TEXT', | ||
'is_custom': False | ||
}, | ||
{ | ||
'field_name': 'spent_at', | ||
'type': 'DATE', | ||
'is_custom': False | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
|
||
from datetime import datetime, timezone | ||
import logging | ||
|
||
from django.utils.module_loading import import_string | ||
|
||
from apps.workspaces.models import Workspace, Sage300Credential | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
logger.level = logging.INFO | ||
|
||
|
||
# Import your Workspace and Sage300Credential models here | ||
# Also, make sure you have 'logger' defined and imported from a logging module | ||
def check_interval_and_sync_dimension(workspace: Workspace, sage300_credential: Sage300Credential) -> bool: | ||
""" | ||
Check the synchronization interval and trigger dimension synchronization if needed. | ||
:param workspace: Workspace Instance | ||
:param sage300_credential: Sage300Credential Instance | ||
:return: True if synchronization is triggered, False if not | ||
""" | ||
|
||
if workspace.destination_synced_at: | ||
# Calculate the time interval since the last destination sync | ||
time_interval = datetime.now(timezone.utc) - workspace.destination_synced_at | ||
|
||
if workspace.destination_synced_at is None or time_interval.days > 0: | ||
# If destination_synced_at is None or the time interval is greater than 0 days, trigger synchronization | ||
sync_dimensions(sage300_credential, workspace.id) | ||
return True | ||
|
||
return False | ||
|
||
|
||
def sync_dimensions(sage300_credential: Sage300Credential, workspace_id: int) -> None: | ||
""" | ||
Synchronize various dimensions with Sage 300 using the provided credentials. | ||
:param sage300_credential: Sage300Credential Instance | ||
:param workspace_id: ID of the workspace | ||
This function syncs dimensions like accounts, vendors, commitments, jobs, categories, and cost codes. | ||
""" | ||
|
||
# Initialize the Sage 300 connection using the provided credentials and workspace ID | ||
sage300_connection = import_string('apps.sage300.utils.SageDesktopConnector')(sage300_credential, workspace_id) | ||
|
||
# List of dimensions to sync | ||
dimensions = ['accounts', 'vendors', 'commitments', 'jobs', 'categories', 'cost_codes'] | ||
|
||
for dimension in dimensions: | ||
try: | ||
# Dynamically call the sync method based on the dimension | ||
sync = getattr(sage300_connection, 'sync_{}'.format(dimension)) | ||
sync() | ||
except Exception as exception: | ||
# Log any exceptions that occur during synchronization | ||
logger.info(exception) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import logging | ||
from django.db.models import Q | ||
from datetime import datetime | ||
from rest_framework import serializers | ||
from rest_framework.response import Response | ||
from rest_framework.views import status | ||
|
||
from fyle_accounting_mappings.models import DestinationAttribute | ||
|
||
from apps.workspaces.models import Workspace, Sage300Credential | ||
from apps.sage300.helpers import sync_dimensions, check_interval_and_sync_dimension | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
logger.level = logging.INFO | ||
|
||
|
||
class ImportSage300AttributesSerializer(serializers.Serializer): | ||
""" | ||
Import Sage300 Attributes serializer | ||
""" | ||
|
||
def create(self, validated_data): | ||
try: | ||
# Get the workspace ID from the URL kwargs | ||
workspace_id = self.context['request'].parser_context['kwargs']['workspace_id'] | ||
|
||
# Check if the 'refresh' field is provided in the request data | ||
refresh_dimension = self.context['request'].data.get('refresh', False) | ||
|
||
# Retrieve the workspace and Sage 300 credentials | ||
workspace = Workspace.objects.get(pk=workspace_id) | ||
sage_intacct_credentials = Sage300Credential.objects.get( | ||
workspace_id=workspace.id | ||
) | ||
|
||
if refresh_dimension: | ||
# If 'refresh' is true, perform a full sync of dimensions | ||
sync_dimensions(sage_intacct_credentials, workspace.id) | ||
else: | ||
# If 'refresh' is false, check the interval and sync dimension accordingly | ||
check_interval_and_sync_dimension(workspace, sage_intacct_credentials) | ||
|
||
# Update the destination_synced_at field and save the workspace | ||
workspace.destination_synced_at = datetime.now() | ||
workspace.save(update_fields=['destination_synced_at']) | ||
|
||
# Return a success response | ||
return Response(status=status.HTTP_200_OK) | ||
|
||
except Sage300Credential.DoesNotExist: | ||
# Handle the case when Sage 300 credentials are not found or invalid | ||
raise serializers.ValidationError( | ||
{'message': 'Sage300 credentials not found / invalid in workspace'} | ||
) | ||
|
||
except Exception as exception: | ||
# Handle unexpected exceptions and log the error | ||
logger.error( | ||
'Something unexpected happened workspace_id: %s %s', | ||
workspace_id, | ||
exception, | ||
) | ||
# Raise a custom exception or re-raise the original exception | ||
raise | ||
|
||
|
||
class DestinationAttributeSerializer(serializers.Serializer): | ||
attribute_type = serializers.CharField() | ||
display_name = serializers.CharField() | ||
|
||
|
||
class Sage300FieldSerializer(serializers.Serializer): | ||
""" | ||
Sage300 Expense Fields Serializer | ||
""" | ||
|
||
attribute_type = serializers.CharField() | ||
display_name = serializers.CharField() | ||
|
||
def format_sage300_fields(self, workspace_id): | ||
attribute_types = [ | ||
"VENDOR", | ||
"ACCOUNT", | ||
"JOB", | ||
"CATEGORY", | ||
"COST_CODE", | ||
"PAYMENT", | ||
] | ||
attributes = ( | ||
DestinationAttribute.objects.filter( | ||
~Q(attribute_type__in=attribute_types), | ||
workspace_id=workspace_id, | ||
) | ||
.values("attribute_type", "display_name") | ||
.distinct() | ||
) | ||
|
||
serialized_attributes = Sage300FieldSerializer(attributes, many=True).data | ||
|
||
# Adding "Job" by default since it can be supported even if it doesn't exist | ||
attributes_list = list(serialized_attributes) | ||
attributes_list.append({"attribute_type": "JOB", "display_name": "Job"}) | ||
|
||
return attributes_list |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from django.urls import path | ||
|
||
from apps.sage300.views import ImportSage300AttributesView, Sage300FieldsView | ||
|
||
|
||
urlpatterns = [ | ||
path( | ||
"import_attributes/", | ||
ImportSage300AttributesView.as_view(), | ||
name="import-sage300-attributes", | ||
), | ||
path("fields/", Sage300FieldsView.as_view(), name="sage300-fields"), | ||
] |
Oops, something went wrong.