Skip to content

Commit

Permalink
add support for code prepending in CATEGORY (#208)
Browse files Browse the repository at this point in the history
* add support for code prepending in CATEGORY

* rename the helper method

* rename the helper method

* add support for code prepending in MERCHANT (#209)

* add support for code prepending in MERCHANT

* change the callback methods are sent

* bug fix

* rename the helper method

* fix callback method mapping of vendor

* Code naming support cost center (#210)

* add support for code prepending in COST_CENTER

* rename the helper method

* add support for code prepending in CUSTOM attribute (#211)

* add support for code prepending in CUSTOM attribute

* improve test case
  • Loading branch information
Hrishabh17 authored Jul 23, 2024
1 parent 2cc2294 commit 320f0bf
Show file tree
Hide file tree
Showing 13 changed files with 879 additions and 23 deletions.
78 changes: 76 additions & 2 deletions apps/mappings/imports/modules/categories.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import logging
from datetime import datetime
from typing import List
from typing import List, Dict
from apps.workspaces.models import ImportSetting, FyleCredential
from apps.mappings.imports.modules.base import Base
from fyle_accounting_mappings.models import DestinationAttribute, CategoryMapping
from apps.mappings.helpers import prepend_code_to_name
from fyle_integrations_platform_connector import PlatformConnector
from fyle_accounting_mappings.models import DestinationAttribute, ExpenseAttribute, CategoryMapping

logger = logging.getLogger(__name__)
logger.level = logging.INFO


class Category(Base):
Expand Down Expand Up @@ -82,3 +89,70 @@ def create_mappings(self):
self.destination_field,
self.workspace_id,
)


def disable_categories(workspace_id: int, categories_to_disable: Dict, *args, **kwargs):
"""
categories_to_disable object format:
{
'destination_id': {
'value': 'old_category_name',
'updated_value': 'new_category_name',
'code': 'old_code',
'update_code': 'new_code' ---- if the code is updated else same as code
}
}
"""
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials=fyle_credentials)

use_code_in_naming = ImportSetting.objects.filter(workspace_id=workspace_id, import_code_fields__contains=['ACCOUNT']).first()
category_account_mapping = CategoryMapping.objects.filter(
workspace_id=workspace_id,
destination_account__destination_id__in=categories_to_disable.keys()
)

logger.info(f"Deleting Category-Account Mappings | WORKSPACE_ID: {workspace_id} | COUNT: {category_account_mapping.count()}")
category_account_mapping.delete()

category_values = []
for category_map in categories_to_disable.values():
category_name = prepend_code_to_name(prepend_code_in_name=use_code_in_naming, value=category_map['value'], code=category_map['code'])
category_values.append(category_name)

filters = {
'workspace_id': workspace_id,
'attribute_type': 'CATEGORY',
'value__in': category_values,
'active': True
}

# Expense attribute value map is as follows: {old_category_name: destination_id}
expense_attribute_value_map = {}
for k, v in categories_to_disable.items():
category_name = prepend_code_to_name(prepend_code_in_name=use_code_in_naming, value=v['value'], code=v['code'])
expense_attribute_value_map[category_name] = k

expense_attributes = ExpenseAttribute.objects.filter(**filters)

bulk_payload = []
for expense_attribute in expense_attributes:
code = expense_attribute_value_map.get(expense_attribute.value, None)
if code:
payload = {
'name': expense_attribute.value,
'code': code,
'is_enabled': False,
'id': expense_attribute.source_id
}
bulk_payload.append(payload)
else:
logger.error(f"Category not found in categories_to_disable: {expense_attribute.value}")

if bulk_payload:
logger.info(f"Disabling Category in Fyle | WORKSPACE_ID: {workspace_id} | COUNT: {len(bulk_payload)}")
platform.categories.post_bulk(bulk_payload)
else:
logger.info(f"No Categories to Disable in Fyle | WORKSPACE_ID: {workspace_id}")

return bulk_payload
85 changes: 83 additions & 2 deletions apps/mappings/imports/modules/cost_centers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import logging
from datetime import datetime
from typing import List
from typing import List, Dict
from apps.mappings.imports.modules.base import Base
from fyle_accounting_mappings.models import DestinationAttribute
from fyle_accounting_mappings.models import DestinationAttribute, ExpenseAttribute, MappingSetting, Mapping
from apps.workspaces.models import FyleCredential, ImportSetting
from fyle_integrations_platform_connector import PlatformConnector
from apps.mappings.helpers import prepend_code_to_name

logger = logging.getLogger(__name__)
logger.level = logging.INFO


class CostCenter(Base):
Expand Down Expand Up @@ -56,3 +63,77 @@ def construct_fyle_payload(
payload.append(cost_center)

return payload


def disable_cost_centers(workspace_id: int, cost_centers_to_disable: Dict, *args, **kwargs):
"""
cost_centers_to_disable object format:
{
'destination_id': {
'value': 'old_cost_center_name',
'updated_value': 'new_cost_center_name',
'code': 'old_code',
'update_code': 'new_code' ---- if the code is updated else same as code
}
}
"""
destination_type = MappingSetting.objects.get(workspace_id=workspace_id, source_field='COST_CENTER').destination_field
use_code_in_naming = ImportSetting.objects.filter(workspace_id=workspace_id, import_code_fields__contains=[destination_type]).first()

cost_center_mappings = Mapping.objects.filter(
workspace_id=workspace_id,
source_type='COST_CENTER',
destination_type=destination_type,
destination_id__destination_id__in=cost_centers_to_disable.keys()
)

logger.info(f"Deleting Cost Center Mappings | WORKSPACE_ID: {workspace_id} | COUNT: {cost_center_mappings.count()}")
cost_center_mappings.delete()

fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials=fyle_credentials)

cost_center_values = []
for cost_center_map in cost_centers_to_disable.values():
cost_center_name = prepend_code_to_name(prepend_code_in_name=use_code_in_naming, value=cost_center_map['value'], code=cost_center_map['code'])
cost_center_values.append(cost_center_name)

filters = {
'workspace_id': workspace_id,
'attribute_type': 'COST_CENTER',
'value__in': cost_center_values,
'active': True
}

expense_attribute_value_map = {}
for k, v in cost_centers_to_disable.items():
cost_center_name = prepend_code_to_name(prepend_code_in_name=use_code_in_naming, value=v['value'], code=v['code'])
expense_attribute_value_map[cost_center_name] = k

expense_attributes = ExpenseAttribute.objects.filter(**filters)

bulk_payload = []
for expense_attribute in expense_attributes:
code = expense_attribute_value_map.get(expense_attribute.value, None)
if code:
payload = {
'name': expense_attribute.value,
'code': code,
'is_enabled': False,
'id': expense_attribute.source_id,
'description': 'Cost Center - {0}, Id - {1}'.format(
expense_attribute.value,
code
)
}
bulk_payload.append(payload)
else:
logger.error(f"Cost Center with value {expense_attribute.value} not found | WORKSPACE_ID: {workspace_id}")

if bulk_payload:
logger.info(f"Disabling Cost Center in Fyle | WORKSPACE_ID: {workspace_id} | COUNT: {len(bulk_payload)}")
platform.cost_centers.post_bulk(bulk_payload)
else:
logger.info(f"No Cost Center to Disable in Fyle | WORKSPACE_ID: {workspace_id}")

return bulk_payload
32 changes: 31 additions & 1 deletion apps/mappings/imports/modules/expense_custom_fields.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import logging
from datetime import datetime
from typing import List, Dict
from apps.mappings.imports.modules.base import Base
from fyle_accounting_mappings.models import (
DestinationAttribute,
ExpenseAttribute
ExpenseAttribute,
Mapping
)
from apps.mappings.exceptions import handle_import_exceptions
from apps.mappings.models import ImportLog
from fyle_integrations_platform_connector import PlatformConnector
from apps.workspaces.models import FyleCredential
from apps.mappings.constants import FYLE_EXPENSE_SYSTEM_FIELDS

logger = logging.getLogger(__name__)
logger.level = logging.INFO


class ExpenseCustomField(Base):
"""
Expand Down Expand Up @@ -176,3 +181,28 @@ def import_destination_attribute_to_fyle(self, import_log: ImportLog):
self.sync_expense_attributes(platform)

self.create_mappings()


def disable_custom_attributes(workspace_id: int, custom_fields_to_disable: Dict, *args, **kwargs):
"""
custom_fields_to_disable object format:
{
'destination_id': {
'value': 'old_custom_field_name',
'updated_value': 'new_custom_field_name',
'code': 'old_code',
'update_code': 'new_code' ---- if the code is updated else same as code
}
}
Currently JOB is only field that can be imported as Custom Field, may need to
update this function if more fields are added in future
"""
custom_field_mappings = Mapping.objects.filter(
workspace_id=workspace_id,
destination_type='JOB',
destination_id__destination_id__in=custom_fields_to_disable.keys()
)

logger.info(f"Deleting Custom Field Mappings | WORKSPACE_ID: {workspace_id} | COUNT: {custom_field_mappings.count()}")
custom_field_mappings.delete()
50 changes: 47 additions & 3 deletions apps/mappings/imports/modules/merchants.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import logging
from datetime import datetime
from typing import List
from typing import List, Dict
from apps.mappings.imports.modules.base import Base
from fyle_accounting_mappings.models import DestinationAttribute
from fyle_accounting_mappings.models import DestinationAttribute, ExpenseAttribute
from apps.mappings.models import ImportLog
from apps.mappings.exceptions import handle_import_exceptions
from apps.workspaces.models import FyleCredential
from apps.workspaces.models import FyleCredential, ImportSetting
from fyle_integrations_platform_connector import PlatformConnector
from apps.mappings.helpers import prepend_code_to_name

logger = logging.getLogger(__name__)
logger.level = logging.INFO


class Merchant(Base):
Expand Down Expand Up @@ -68,3 +73,42 @@ def import_destination_attribute_to_fyle(self, import_log: ImportLog):
self.construct_payload_and_import_to_fyle(platform, import_log)

self.sync_expense_attributes(platform)


def disable_merchants(workspace_id: int, merchants_to_disable: Dict, *args, **kwargs):
"""
merchants_to_disable object format:
{
'destination_id': {
'value': 'old_merchant_name',
'updated_value': 'new_merchant_name',
'code': 'old_code',
'update_code': 'new_code' ---- if the code is updated else same as code
}
}
"""
fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id)
platform = PlatformConnector(fyle_credentials=fyle_credentials)
use_code_in_naming = ImportSetting.objects.filter(workspace_id = workspace_id, import_code_fields__contains=['VENDOR']).first()

merchant_values = []
for merchant_map in merchants_to_disable.values():
merchant_name = prepend_code_to_name(prepend_code_in_name=use_code_in_naming, value=merchant_map['value'], code=merchant_map['code'])
merchant_values.append(merchant_name)

filters = {
'workspace_id': workspace_id,
'attribute_type': 'MERCHANT',
'value__in': merchant_values,
'active': True
}

bulk_payload = ExpenseAttribute.objects.filter(**filters).values_list('value', flat=True)

if bulk_payload:
logger.info(f"Disabling Merchants in Fyle | WORKSPACE_ID: {workspace_id} | COUNT: {len(bulk_payload)}")
platform.merchants.post(bulk_payload, delete_merchants=True)
else:
logger.info(f"No Merchants to Disable in Fyle | WORKSPACE_ID: {workspace_id}")

return bulk_payload
8 changes: 6 additions & 2 deletions apps/mappings/imports/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,19 @@ def chain_import_fields_to_fyle(workspace_id):
'apps.mappings.imports.tasks.trigger_import_via_schedule',
workspace_id,
'ACCOUNT',
'CATEGORY'
'CATEGORY',
False,
True if 'ACCOUNT' in import_code_fields else False
)

if import_settings.import_vendors_as_merchants:
chain.append(
'apps.mappings.imports.tasks.trigger_import_via_schedule',
workspace_id,
'VENDOR',
'MERCHANT'
'MERCHANT',
False,
True if 'VENDOR' in import_code_fields else False
)

for mapping_setting in mapping_settings:
Expand Down
3 changes: 1 addition & 2 deletions apps/sage300/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,10 @@ def disable_projects(workspace_id: int, projects_to_disable: Dict, *args, **kwar
'is_enabled': False,
'id': expense_attribute.source_id
}
bulk_payload.append(payload)
else:
logger.error(f"Project with value {expense_attribute.value} not found | WORKSPACE_ID: {workspace_id}")

bulk_payload.append(payload)

if bulk_payload:
logger.info(f"Disabling Projects in Fyle | WORKSPACE_ID: {workspace_id} | COUNT: {len(bulk_payload)}")
platform.projects.post_bulk(bulk_payload)
Expand Down
25 changes: 21 additions & 4 deletions apps/sage300/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from django.utils.module_loading import import_string
from fyle_accounting_mappings.models import DestinationAttribute
from fyle_accounting_mappings.models import DestinationAttribute, MappingSetting
from apps.workspaces.models import Sage300Credential
from sage_desktop_sdk.sage_desktop_sdk import SageDesktopSDK
from apps.sage300.models import CostCategory
Expand All @@ -11,6 +11,15 @@
logger.level = logging.INFO


ATTRIBUTE_CALLBACK_MAP = {
'PROJECT': 'apps.sage300.helpers.disable_projects',
'CATEGORY': 'apps.mappings.imports.modules.categories.disable_categories',
'MERCHANT': 'apps.mappings.imports.modules.merchants.disable_merchants',
'COST_CENTER': 'apps.mappings.imports.modules.cost_centers.disable_cost_centers',
'CUSTOM': 'apps.mappings.imports.modules.expense_custom_fields.disable_custom_attributes'
}


class SageDesktopConnector:
"""
Sage300 utility functions for syncing data from Sage Desktop SDK to your application
Expand Down Expand Up @@ -132,6 +141,15 @@ def _sync_data(self, data_gen, attribute_type, display_name, workspace_id, field
:param workspace_id: ID of the workspace
:param field_names: Names of fields to include in detail
"""
source_type = None
mapping_setting = MappingSetting.objects.filter(workspace_id=workspace_id, destination_field=attribute_type).first()
if mapping_setting:
if attribute_type == 'VENDOR':
source_type = 'MERCHANT'
elif mapping_setting.is_custom:
source_type = 'CUSTOM'
else:
source_type = mapping_setting.source_field

if is_generator:
for data in data_gen:
Expand All @@ -144,14 +162,13 @@ def _sync_data(self, data_gen, attribute_type, display_name, workspace_id, field
if destination_attr:
destination_attributes.append(destination_attr)

if attribute_type == 'JOB':
project_disable_callback_path = 'apps.sage300.helpers.disable_projects'
if source_type in ATTRIBUTE_CALLBACK_MAP.keys():
DestinationAttribute.bulk_create_or_update_destination_attributes(
destination_attributes,
attribute_type,
workspace_id,
True,
attribute_disable_callback_path=project_disable_callback_path
attribute_disable_callback_path=ATTRIBUTE_CALLBACK_MAP[source_type]
)
else:
DestinationAttribute.bulk_create_or_update_destination_attributes(
Expand Down
Loading

0 comments on commit 320f0bf

Please sign in to comment.