-
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.
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
- Loading branch information
Showing
9 changed files
with
533 additions
and
12 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
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,59 @@ | ||
from datetime import datetime | ||
from typing import List | ||
|
||
from fyle_accounting_mappings.models import DestinationAttribute | ||
|
||
from apps.mappings.imports.modules.base import Base | ||
|
||
|
||
class CostCenter(Base): | ||
""" | ||
Class for Cost Center module | ||
""" | ||
|
||
def __init__(self, workspace_id: int, destination_field: str, sync_after: datetime): | ||
super().__init__( | ||
workspace_id=workspace_id, | ||
source_field="COST_CENTER", | ||
destination_field=destination_field, | ||
platform_class_name="cost_centers", | ||
sync_after=sync_after, | ||
) | ||
|
||
def trigger_import(self): | ||
""" | ||
Trigger import for Cost Center module | ||
""" | ||
self.check_import_log_and_start_import() | ||
|
||
def construct_fyle_payload( | ||
self, | ||
paginated_destination_attributes: List[DestinationAttribute], | ||
existing_fyle_attributes_map: object, | ||
is_auto_sync_status_allowed: bool | ||
): | ||
""" | ||
Construct Fyle payload for CostCenter module | ||
:param paginated_destination_attributes: List of paginated destination attributes | ||
:param existing_fyle_attributes_map: Existing Fyle attributes map | ||
:param is_auto_sync_status_allowed: Is auto sync status allowed | ||
:return: Fyle payload | ||
""" | ||
payload = [] | ||
|
||
for attribute in paginated_destination_attributes: | ||
cost_center = { | ||
'name': attribute.value, | ||
'code': attribute.destination_id, | ||
'is_enabled': True if attribute.active is None else attribute.active, | ||
'description': 'Cost Center - {0}, Id - {1}'.format( | ||
attribute.value, | ||
attribute.destination_id | ||
) | ||
} | ||
|
||
# Create a new cost-center if it does not exist in Fyle | ||
if attribute.value.lower() not in existing_fyle_attributes_map: | ||
payload.append(cost_center) | ||
|
||
return payload |
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 |
---|---|---|
@@ -1,5 +1,176 @@ | ||
from datetime import datetime | ||
from typing import Dict, List | ||
|
||
from fyle_accounting_mappings.models import DestinationAttribute, ExpenseAttribute | ||
from fyle_integrations_platform_connector import PlatformConnector | ||
|
||
from apps.mappings.constants import FYLE_EXPENSE_SYSTEM_FIELDS | ||
from apps.mappings.exceptions import handle_import_exceptions | ||
from apps.mappings.imports.modules.base import Base | ||
from apps.mappings.models import ImportLog | ||
from apps.workspaces.models import FyleCredential | ||
|
||
|
||
class ExpenseCustomField(Base): | ||
pass | ||
""" | ||
Class for ExepenseCustomField module | ||
""" | ||
def __init__(self, workspace_id: int, source_field: str, destination_field: str, sync_after: datetime): | ||
super().__init__( | ||
workspace_id=workspace_id, | ||
source_field=source_field, | ||
destination_field=destination_field, | ||
platform_class_name='expense_custom_fields', | ||
sync_after=sync_after | ||
) | ||
|
||
def trigger_import(self): | ||
""" | ||
Trigger import for ExepenseCustomField module | ||
""" | ||
self.check_import_log_and_start_import() | ||
|
||
def construct_custom_field_placeholder(self, source_placeholder: str, fyle_attribute: str, existing_attribute: Dict): | ||
""" | ||
Construct placeholder for custom field | ||
:param source_placeholder: Placeholder from mapping settings | ||
:param fyle_attribute: Fyle attribute | ||
:param existing_attribute: Existing attribute | ||
""" | ||
new_placeholder = None | ||
placeholder = None | ||
|
||
if existing_attribute: | ||
placeholder = existing_attribute['placeholder'] if 'placeholder' in existing_attribute else None | ||
|
||
# Here is the explanation of what's happening in the if-else ladder below | ||
# source_field is the field that's save in mapping settings, this field user may or may not fill in the custom field form | ||
# placeholder is the field that's saved in the detail column of destination attributes | ||
# fyle_attribute is what we're constructing when both of these fields would not be available | ||
|
||
if not (source_placeholder or placeholder): | ||
# If source_placeholder and placeholder are both None, then we're creating adding a self constructed placeholder | ||
new_placeholder = 'Select {0}'.format(fyle_attribute) | ||
elif not source_placeholder and placeholder: | ||
# If source_placeholder is None but placeholder is not, then we're choosing same place holder as 1 in detail section | ||
new_placeholder = placeholder | ||
elif source_placeholder and not placeholder: | ||
# If source_placeholder is not None but placeholder is None, then we're choosing the placeholder as filled by user in form | ||
new_placeholder = source_placeholder | ||
else: | ||
# Else, we're choosing the placeholder as filled by user in form or None | ||
new_placeholder = source_placeholder | ||
|
||
return new_placeholder | ||
|
||
def construct_fyle_expense_custom_field_payload( | ||
self, | ||
business_central_attributes: List[DestinationAttribute], | ||
platform: PlatformConnector, | ||
source_placeholder: str = None | ||
): | ||
""" | ||
Construct payload for expense custom fields | ||
:param business_central_attributes: List of destination attributes | ||
:param platform: PlatformConnector object | ||
:param source_placeholder: Placeholder from mapping settings | ||
""" | ||
fyle_expense_custom_field_options = [] | ||
fyle_attribute = self.source_field | ||
|
||
[fyle_expense_custom_field_options.append(business_central_attribute.value) for business_central_attribute in business_central_attributes] | ||
|
||
if fyle_attribute.lower() not in FYLE_EXPENSE_SYSTEM_FIELDS: | ||
existing_attribute = ExpenseAttribute.objects.filter( | ||
attribute_type=fyle_attribute, workspace_id=self.workspace_id).values_list('detail', flat=True).first() | ||
|
||
custom_field_id = None | ||
|
||
if existing_attribute is not None: | ||
custom_field_id = existing_attribute['custom_field_id'] | ||
|
||
fyle_attribute = fyle_attribute.replace('_', ' ').title() | ||
placeholder = self.construct_custom_field_placeholder(source_placeholder, fyle_attribute, existing_attribute) | ||
|
||
expense_custom_field_payload = { | ||
'field_name': fyle_attribute, | ||
'type': 'SELECT', | ||
'is_enabled': True, | ||
'is_mandatory': False, | ||
'placeholder': placeholder, | ||
'options': fyle_expense_custom_field_options, | ||
'code': None | ||
} | ||
|
||
if custom_field_id: | ||
expense_field = platform.expense_custom_fields.get_by_id(custom_field_id) | ||
expense_custom_field_payload['id'] = custom_field_id | ||
expense_custom_field_payload['is_mandatory'] = expense_field['is_mandatory'] | ||
|
||
return expense_custom_field_payload | ||
|
||
# construct_payload_and_import_to_fyle method is overridden | ||
def construct_payload_and_import_to_fyle( | ||
self, | ||
platform: PlatformConnector, | ||
import_log: ImportLog, | ||
source_placeholder: str = None | ||
): | ||
""" | ||
Construct Payload and Import to fyle in Batches | ||
""" | ||
filters = self.construct_attributes_filter(self.destination_field) | ||
|
||
destination_attributes_count = DestinationAttribute.objects.filter(**filters).count() | ||
|
||
# If there are no destination attributes, mark the import as complete | ||
if destination_attributes_count == 0: | ||
import_log.status = 'COMPLETE' | ||
import_log.last_successful_run_at = datetime.now() | ||
import_log.error_log = [] | ||
import_log.total_batches_count = 0 | ||
import_log.processed_batches_count = 0 | ||
import_log.save() | ||
return | ||
else: | ||
import_log.total_batches_count = 1 | ||
import_log.save() | ||
|
||
destination_attributes = DestinationAttribute.objects.filter(**filters) | ||
destination_attributes_without_duplicates = self.remove_duplicate_attributes(destination_attributes) | ||
platform_class = self.get_platform_class(platform) | ||
|
||
fyle_payload = self.construct_fyle_expense_custom_field_payload( | ||
destination_attributes_without_duplicates, | ||
platform, | ||
source_placeholder | ||
) | ||
|
||
self.post_to_fyle_and_sync( | ||
fyle_payload=fyle_payload, | ||
resource_class=platform_class, | ||
is_last_batch=True, | ||
import_log=import_log | ||
) | ||
|
||
# import_destination_attribute_to_fyle method is overridden | ||
@handle_import_exceptions | ||
def import_destination_attribute_to_fyle(self, import_log: ImportLog): | ||
""" | ||
Import destiantion_attributes field to Fyle and Auto Create Mappings | ||
:param import_log: ImportLog object | ||
""" | ||
|
||
fyle_credentials = FyleCredential.objects.get(workspace_id=self.workspace_id) | ||
platform = PlatformConnector(fyle_credentials=fyle_credentials) | ||
|
||
self.sync_destination_attributes(self.destination_field) | ||
|
||
self.construct_payload_and_import_to_fyle( | ||
platform=platform, | ||
import_log=import_log | ||
) | ||
|
||
self.sync_expense_attributes(platform) | ||
|
||
self.create_mappings() |
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,63 @@ | ||
from datetime import datetime | ||
from typing import List | ||
|
||
from fyle_accounting_mappings.models import DestinationAttribute | ||
|
||
from apps.mappings.imports.modules.base import Base | ||
|
||
|
||
class Project(Base): | ||
""" | ||
Class for Project module | ||
""" | ||
|
||
def __init__(self, workspace_id: int, destination_field: str, sync_after: datetime): | ||
super().__init__( | ||
workspace_id=workspace_id, | ||
source_field="PROJECT", | ||
destination_field=destination_field, | ||
platform_class_name="projects", | ||
sync_after=sync_after, | ||
) | ||
|
||
def trigger_import(self): | ||
""" | ||
Trigger import for Project module | ||
""" | ||
self.check_import_log_and_start_import() | ||
|
||
def construct_fyle_payload( | ||
self, | ||
paginated_destination_attributes: List[DestinationAttribute], | ||
existing_fyle_attributes_map: object, | ||
is_auto_sync_status_allowed: bool | ||
): | ||
""" | ||
Construct Fyle payload for Project module | ||
:param paginated_destination_attributes: List of paginated destination attributes | ||
:param existing_fyle_attributes_map: Existing Fyle attributes map | ||
:param is_auto_sync_status_allowed: Is auto sync status allowed | ||
:return: Fyle payload | ||
""" | ||
payload = [] | ||
|
||
for attribute in paginated_destination_attributes: | ||
project = { | ||
'name': attribute.value, | ||
'code': attribute.destination_id, | ||
'description': 'Business Central Project - {0}, Id - {1}'.format( | ||
attribute.value, | ||
attribute.destination_id | ||
), | ||
'is_enabled': True if attribute.active is None else attribute.active | ||
} | ||
|
||
# Create a new project if it does not exist in Fyle | ||
if attribute.value.lower() not in existing_fyle_attributes_map: | ||
payload.append(project) | ||
# Disable the existing project in Fyle if auto-sync status is allowed and the destination_attributes is inactive | ||
elif is_auto_sync_status_allowed and not attribute.active: | ||
project['id'] = existing_fyle_attributes_map[attribute.value.lower()] | ||
payload.append(project) | ||
|
||
return payload |
Oops, something went wrong.