-
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.
Business central SDK support added (#24)
* Fyle Import attributes API * Fyle fields apis * Fyle expense fields apis * Test case resolved * Test case resolved * Test case resolved * Business central SDK support added * Business central SDK support added * flake8 resolved * flake8 resolved * Business Central creds apis (#25) * Business Central creds apis * Test cases added * Test cases added * code removed
- Loading branch information
Showing
11 changed files
with
340 additions
and
5 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,128 @@ | ||
from dynamics.core.client import Dynamics | ||
from fyle_accounting_mappings.models import DestinationAttribute | ||
|
||
from apps.workspaces.models import BusinessCentralCredentials, Workspace | ||
from ms_business_central_api import settings | ||
|
||
|
||
class BusinessCentralConnector: | ||
""" | ||
Business Central Utility Functions | ||
""" | ||
|
||
def __init__(self, credentials_object: BusinessCentralCredentials, workspace_id: int): | ||
client_id = settings.BUSINESS_CENTRAL_CLIENT_ID | ||
client_secret = settings.BUSINESS_CENTRAL_CLIENT_SECRET | ||
environment = settings.BUSINESS_CENTRAL_ENVIRONMENT | ||
refresh_token = credentials_object.refresh_token | ||
|
||
self.connection = Dynamics( | ||
enviroment=environment, | ||
client_id=client_id, | ||
client_secret=client_secret, | ||
refresh_token=refresh_token, | ||
) | ||
|
||
self.workspace_id = workspace_id | ||
|
||
credentials_object.refresh_token = self.connection.refresh_token | ||
credentials_object.save() | ||
|
||
def _create_destination_attribute(self, attribute_type, display_name, value, destination_id, active, detail): | ||
""" | ||
Create a destination attribute object | ||
:param attribute_type: Type of the attribute | ||
:param display_name: Display name for the attribute | ||
:param value: Value of the attribute | ||
:param destination_id: ID of the destination | ||
:param active: Whether the attribute is active | ||
:param detail: Details related to the attribute | ||
:return: A destination attribute dictionary | ||
""" | ||
return { | ||
'attribute_type': attribute_type, | ||
'display_name': display_name, | ||
'value': value, | ||
'destination_id': destination_id, | ||
'active': active, | ||
'detail': detail | ||
} | ||
|
||
def _sync_data(self, data, attribute_type, display_name, workspace_id, field_names): | ||
""" | ||
Synchronize data from MS Dynamics SDK to your application | ||
:param data: Data to synchronize | ||
:param attribute_type: Type of the attribute | ||
:param display_name: Display name for the data | ||
:param workspace_id: ID of the workspace | ||
:param field_names: Names of fields to include in detail | ||
""" | ||
|
||
destination_attributes = [] | ||
|
||
for item in data: | ||
detail = {field: getattr(item, field) for field in field_names} | ||
destination_attributes.append(self._create_destination_attribute( | ||
attribute_type, | ||
display_name, | ||
item.name, | ||
item.id, | ||
item.is_active, | ||
detail | ||
)) | ||
|
||
DestinationAttribute.bulk_create_or_update_destination_attributes( | ||
destination_attributes, attribute_type, workspace_id, True) | ||
|
||
def sync_companies(self): | ||
""" | ||
sync business central companies | ||
""" | ||
companies = self.connection.companies.get_all() | ||
|
||
self._sync_data(companies, 'COMPANY', 'company', self.workspace_id) | ||
return [] | ||
|
||
def sync_accounts(self): | ||
""" | ||
Synchronize accounts from MS Dynamics SDK to your application | ||
""" | ||
workspace = Workspace.objects.get(id=self.workspace_id) | ||
self.connection.company_id = workspace.business_central_company_id | ||
|
||
accounts = self.connection.accounts.get_all() | ||
self._sync_data(accounts, 'ACCOUNT', 'accounts', self.workspace_id) | ||
return [] | ||
|
||
def sync_vendors(self): | ||
""" | ||
Synchronize vendors from MS Dynamics SDK to your application | ||
""" | ||
workspace = Workspace.objects.get(id=self.workspace_id) | ||
self.connection.company_id = workspace.business_central_company_id | ||
|
||
vendors = self.connection.vendors.get_all() | ||
self._sync_data(vendors, 'VENDOR', 'vendor', self.workspace_id) | ||
return [] | ||
|
||
def sync_employees(self): | ||
""" | ||
Synchronize employees from MS Dynamics SDK to your application | ||
""" | ||
workspace = Workspace.objects.get(id=self.workspace_id) | ||
self.connection.company_id = workspace.business_central_company_id | ||
|
||
employees = self.connection.employees.get_all() | ||
self._sync_data(employees, 'EMPLOYEE', 'employee', self.workspace_id) | ||
return [] | ||
|
||
def sync_locations(self): | ||
""" | ||
Synchronize locations from MS Dynamics SDK to your application | ||
""" | ||
workspace = Workspace.objects.get(id=self.workspace_id) | ||
self.connection.company_id = workspace.business_central_company_id | ||
|
||
locations = self.connection.locations.get_all() | ||
self._sync_data(locations, 'LOCATION', 'location', self.workspace_id) | ||
return [] |
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,97 @@ | ||
import logging | ||
import base64 | ||
import requests | ||
import json | ||
|
||
from django.conf import settings | ||
from future.moves.urllib.parse import urlencode | ||
from dynamics.exceptions.dynamics_exceptions import InternalServerError, InvalidTokenError | ||
|
||
from apps.workspaces.models import BusinessCentralCredentials, Workspace | ||
from apps.business_central.utils import BusinessCentralConnector | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def generate_token(authorization_code: str, redirect_uri: str = None) -> str: | ||
api_data = { | ||
"grant_type": "authorization_code", | ||
"code": authorization_code, | ||
"redirect_uri": settings.BUSINESS_CENTRAL_REDIRECT_URI | ||
if not redirect_uri | ||
else redirect_uri, | ||
} | ||
|
||
auth = "{0}:{1}".format(settings.BUSINESS_CENTRAL_ID, settings.BUSINESS_CENTRAL_SECRET) | ||
auth = base64.b64encode(auth.encode("utf-8")) | ||
|
||
request_header = { | ||
"Accept": "application/json", | ||
"Content-type": "application/x-www-form-urlencoded", | ||
"Authorization": "Basic {0}".format(str(auth.decode())), | ||
} | ||
|
||
token_url = settings.BUSINESS_CENTRAL_TOKEN_URI | ||
response = requests.post( | ||
url=token_url, data=urlencode(api_data), headers=request_header | ||
) | ||
return response | ||
|
||
|
||
def generate_business_central_refresh_token(authorization_code: str, redirect_uri: str = None) -> str: | ||
""" | ||
Generate Business Central refresh token from authorization code | ||
""" | ||
response = generate_token(authorization_code, redirect_uri) | ||
|
||
if response.status_code == 200: | ||
successful_response = json.loads(response.text) | ||
return successful_response["refresh_token"] | ||
|
||
elif response.status_code == 401: | ||
raise InvalidTokenError( | ||
"Wrong client secret or/and refresh token", response.text | ||
) | ||
|
||
elif response.status_code == 500: | ||
raise InternalServerError("Internal server error", response.text) | ||
|
||
|
||
def connect_business_central(authorization_code, redirect_uri, workspace_id): | ||
if redirect_uri: | ||
refresh_token = generate_business_central_refresh_token(authorization_code, redirect_uri) | ||
else: | ||
refresh_token = generate_business_central_refresh_token(authorization_code) | ||
business_central_credentials = BusinessCentralCredentials.objects.filter(workspace_id=workspace_id).first() | ||
|
||
workspace = Workspace.objects.get(pk=workspace_id) | ||
|
||
if not business_central_credentials: | ||
business_central_credentials = BusinessCentralCredentials.objects.create( | ||
refresh_token=refresh_token, workspace_id=workspace_id | ||
) | ||
else: | ||
business_central_credentials.refresh_token = refresh_token | ||
business_central_credentials.is_expired = False | ||
business_central_credentials.save() | ||
|
||
if workspace and not workspace.business_central_company_id: | ||
business_central_connector = BusinessCentralConnector(business_central_credentials, workspace_id=workspace_id) | ||
connections = business_central_connector.connection.connections.get_all() | ||
connection = list( | ||
filter( | ||
lambda connection: connection["id"] == workspace.business_central_company_id, | ||
connections, | ||
) | ||
) | ||
|
||
if connection: | ||
workspace.business_central_company_id = connection[0]["id"] | ||
workspace.save() | ||
|
||
if workspace.onboarding_state == "CONNECTION": | ||
workspace.onboarding_state = "EXPORT_SETTINGS" | ||
workspace.save() | ||
|
||
return business_central_credentials |
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
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
Oops, something went wrong.