-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
ca98522
e389d66
1e07ae5
32384e5
e39b202
6eb13ba
4843ad0
9ab47c5
0008017
f0aebe2
c8f1d9e
620b36b
8d18a8a
b9f254b
42a62a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -17,7 +17,7 @@ def __init__(self, credentials_object: BusinessCentralCredentials, workspace_id: | |||||||||||||||||
refresh_token = credentials_object.refresh_token | ||||||||||||||||||
|
||||||||||||||||||
self.connection = Dynamics( | ||||||||||||||||||
enviroment=environment, | ||||||||||||||||||
environment=environment, | ||||||||||||||||||
client_id=client_id, | ||||||||||||||||||
client_secret=client_secret, | ||||||||||||||||||
refresh_token=refresh_token, | ||||||||||||||||||
|
@@ -59,18 +59,20 @@ def _sync_data(self, data, attribute_type, display_name, workspace_id, field_nam | |||||||||||||||||
""" | ||||||||||||||||||
|
||||||||||||||||||
destination_attributes = [] | ||||||||||||||||||
|
||||||||||||||||||
for item in data: | ||||||||||||||||||
detail = {field: getattr(item, field) for field in field_names} | ||||||||||||||||||
detail = {field: item[field] for field in field_names} | ||||||||||||||||||
if (attribute_type == 'EMPLOYEE' and item['status'] == 'Active') or attribute_type == 'LOCATION' or item['blocked'] != True: | ||||||||||||||||||
active = True | ||||||||||||||||||
else: | ||||||||||||||||||
active = False | ||||||||||||||||||
Comment on lines
+64
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logical condition in the - if (attribute_type == 'EMPLOYEE' and item['status'] == 'Active') or attribute_type == 'LOCATION' or item['blocked'] != True:
+ if (attribute_type == 'EMPLOYEE' and item['status'] == 'Active') or (attribute_type == 'LOCATION' and item['blocked'] != True): Committable suggestion
Suggested change
|
||||||||||||||||||
destination_attributes.append(self._create_destination_attribute( | ||||||||||||||||||
attribute_type, | ||||||||||||||||||
display_name, | ||||||||||||||||||
item.name, | ||||||||||||||||||
item.id, | ||||||||||||||||||
item.is_active, | ||||||||||||||||||
item['displayName'], | ||||||||||||||||||
item['id'], | ||||||||||||||||||
active, | ||||||||||||||||||
detail | ||||||||||||||||||
)) | ||||||||||||||||||
|
||||||||||||||||||
DestinationAttribute.bulk_create_or_update_destination_attributes( | ||||||||||||||||||
destination_attributes, attribute_type, workspace_id, True) | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -89,9 +91,10 @@ def sync_accounts(self): | |||||||||||||||||
""" | ||||||||||||||||||
workspace = Workspace.objects.get(id=self.workspace_id) | ||||||||||||||||||
self.connection.company_id = workspace.business_central_company_id | ||||||||||||||||||
field_names = ['category', 'subCategory', 'accountType', 'directPosting', 'lastModifiedDateTime'] | ||||||||||||||||||
|
||||||||||||||||||
accounts = self.connection.accounts.get_all() | ||||||||||||||||||
self._sync_data(accounts, 'ACCOUNT', 'accounts', self.workspace_id) | ||||||||||||||||||
self._sync_data(accounts, 'ACCOUNT', 'accounts', self.workspace_id, field_names) | ||||||||||||||||||
return [] | ||||||||||||||||||
|
||||||||||||||||||
def sync_vendors(self): | ||||||||||||||||||
|
@@ -100,9 +103,10 @@ def sync_vendors(self): | |||||||||||||||||
""" | ||||||||||||||||||
workspace = Workspace.objects.get(id=self.workspace_id) | ||||||||||||||||||
self.connection.company_id = workspace.business_central_company_id | ||||||||||||||||||
field_names = ['email', 'currencyId', 'currencyCode', 'lastModifiedDateTime'] | ||||||||||||||||||
|
||||||||||||||||||
vendors = self.connection.vendors.get_all() | ||||||||||||||||||
self._sync_data(vendors, 'VENDOR', 'vendor', self.workspace_id) | ||||||||||||||||||
self._sync_data(vendors, 'VENDOR', 'vendor', self.workspace_id, field_names) | ||||||||||||||||||
return [] | ||||||||||||||||||
|
||||||||||||||||||
def sync_employees(self): | ||||||||||||||||||
|
@@ -111,9 +115,10 @@ def sync_employees(self): | |||||||||||||||||
""" | ||||||||||||||||||
workspace = Workspace.objects.get(id=self.workspace_id) | ||||||||||||||||||
self.connection.company_id = workspace.business_central_company_id | ||||||||||||||||||
field_names = ['email', 'email', 'personalEmail', 'lastModifiedDateTime'] | ||||||||||||||||||
|
||||||||||||||||||
employees = self.connection.employees.get_all() | ||||||||||||||||||
self._sync_data(employees, 'EMPLOYEE', 'employee', self.workspace_id) | ||||||||||||||||||
self._sync_data(employees, 'EMPLOYEE', 'employee', self.workspace_id, field_names) | ||||||||||||||||||
return [] | ||||||||||||||||||
|
||||||||||||||||||
def sync_locations(self): | ||||||||||||||||||
|
@@ -122,7 +127,8 @@ def sync_locations(self): | |||||||||||||||||
""" | ||||||||||||||||||
workspace = Workspace.objects.get(id=self.workspace_id) | ||||||||||||||||||
self.connection.company_id = workspace.business_central_company_id | ||||||||||||||||||
field_names = ['code', 'city', 'country'] | ||||||||||||||||||
|
||||||||||||||||||
locations = self.connection.locations.get_all() | ||||||||||||||||||
self._sync_data(locations, 'LOCATION', 'location', self.workspace_id) | ||||||||||||||||||
self._sync_data(locations, 'LOCATION', 'location', self.workspace_id, field_names) | ||||||||||||||||||
return [] |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import traceback | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from functools import wraps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from fyle.platform.exceptions import NoPrivilegeError | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
from apps.workspaces.models import FyleCredential | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger = logging.getLogger(__name__) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.level = logging.INFO | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+13
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The + raise Committable suggestion
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,40 @@ def post_request(url, body, refresh_token=None): | |
raise Exception(response.text) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider raising a more specific exception or logging the error to provide better context for debugging purposes. |
||
|
||
|
||
def get_request(url, params, refresh_token): | ||
""" | ||
Create a HTTP get request. | ||
""" | ||
access_token = get_access_token(refresh_token) | ||
api_headers = { | ||
'content-type': 'application/json', | ||
'Authorization': 'Bearer {0}'.format(access_token) | ||
} | ||
api_params = {} | ||
|
||
for k in params: | ||
# ignore all unused params | ||
if not params[k] is None: | ||
p = params[k] | ||
|
||
# convert boolean to lowercase string | ||
if isinstance(p, bool): | ||
p = str(p).lower() | ||
|
||
api_params[k] = p | ||
|
||
response = requests.get( | ||
url, | ||
headers=api_headers, | ||
params=api_params | ||
) | ||
|
||
if response.status_code == 200: | ||
return json.loads(response.text) | ||
Comment on lines
+64
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verify that the response content type is 'application/json' before attempting to parse the response text as JSON to avoid potential errors. |
||
else: | ||
raise Exception(response.text) | ||
|
||
|
||
def get_access_token(refresh_token: str) -> str: | ||
""" | ||
Get access token from fyle | ||
|
@@ -102,3 +136,12 @@ def get_exportable_accounting_exports_ids(workspace_id: int): | |
).values_list('id', flat=True) | ||
|
||
return accounting_export_ids | ||
|
||
|
||
def get_fyle_orgs(refresh_token: str, cluster_domain: str): | ||
""" | ||
Get fyle orgs of a user | ||
""" | ||
api_url = '{0}/api/orgs/'.format(cluster_domain) | ||
|
||
return get_request(api_url, {}, refresh_token) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
""" | ||
All the tasks which are queued into django-q | ||
* User Triggered Async Tasks | ||
* Schedule Triggered Async Tasks | ||
""" | ||
from django_q.tasks import async_task | ||
|
||
from apps.accounting_exports.models import AccountingExport | ||
from apps.fyle.tasks import import_expenses | ||
|
||
|
||
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_expenses', | ||
workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL' | ||
) | ||
return | ||
|
||
import_expenses(workspace_id, accounting_export, 'PERSONAL_CASH_ACCOUNT', 'PERSONAL') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Comment on lines
+12
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hardcoded values 'PERSONAL_CASH_ACCOUNT' and 'PERSONAL' are still present in the There is no error handling in the |
||
|
||
|
||
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_expenses', | ||
workspace_id, accounting_export, 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT', 'CCC' | ||
) | ||
return | ||
|
||
import_expenses(workspace_id, accounting_export, 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT', 'CCC') | ||
Comment on lines
+36
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The hardcoded values 'PERSONAL_CORPORATE_CREDIT_CARD_ACCOUNT' and 'CCC' are still present in the There is no error handling in the |
There was a problem hiding this comment.
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 thedate_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 ifaccounting_export[date_field]
isNone
.Committable suggestion