Skip to content
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

add support for code prepending in CATEGORY #208

Merged
merged 5 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 format_attribute_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):
"""
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 = format_attribute_name(use_code_in_naming=use_code_in_naming, attribute_name=category_map['value'], attribute_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 = format_attribute_name(use_code_in_naming=use_code_in_naming, attribute_name=v['value'], attribute_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
4 changes: 3 additions & 1 deletion apps/mappings/imports/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ 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:
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
11 changes: 8 additions & 3 deletions apps/sage300/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
logger.level = logging.INFO


ATTRIBUTE_CALLBACK_MAP = {
'JOB': 'apps.sage300.helpers.disable_projects',
'ACCOUNT': 'apps.mappings.imports.modules.categories.disable_categories',
}


class SageDesktopConnector:
"""
Sage300 utility functions for syncing data from Sage Desktop SDK to your application
Expand Down Expand Up @@ -144,14 +150,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 attribute_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[attribute_type]
)
else:
DestinationAttribute.bulk_create_or_update_destination_attributes(
Expand Down
25 changes: 25 additions & 0 deletions tests/test_mappings/test_imports/test_modules/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,28 @@ def add_expense_destination_attributes_2():
},
active=True
)


@pytest.fixture()
@pytest.mark.django_db(databases=['default'])
def add_expense_destination_attributes_3():
ExpenseAttribute.objects.create(
workspace_id=1,
attribute_type='CATEGORY',
display_name='Category',
value="123 Sage300",
source_id='10095',
detail='Merchant - Platform APIs, Id - 10085',
active=True
)

DestinationAttribute.objects.create(
workspace_id=1,
attribute_type='ACCOUNT',
display_name='Account',
value="Sage300",
destination_id='10085',
detail='Merchant - Platform APIs, Id - 10085',
active=True,
code='123'
)
17 changes: 17 additions & 0 deletions tests/test_mappings/test_imports/test_modules/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8201,5 +8201,22 @@
'description': 'Sage 300 Project - 123 Integrations CRE, Id - 10082',
'is_enabled': True
}
],
"create_fyle_category_payload_with_code_create_new_case":[
{
'name': 'Internet',
'code': 'Internet',
'is_enabled': True
},
{
'name': 'Meals',
'code': 'Meals',
'is_enabled': True
},
{
'name': '123 Sage300',
'code': '10085',
'is_enabled': True
}
]
}
145 changes: 144 additions & 1 deletion tests/test_mappings/test_imports/test_modules/test_categories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from apps.mappings.imports.modules.categories import Category
from apps.workspaces.models import ImportSetting
from apps.mappings.imports.modules.categories import Category, disable_categories
from fyle_accounting_mappings.models import CategoryMapping, DestinationAttribute, ExpenseAttribute
from tests.test_mappings.test_imports.test_modules.fixtures import data as destination_attributes_data
from .fixtures import data


def test_construct_fyle_payload(
Expand Down Expand Up @@ -82,3 +84,144 @@ def test_create_mappings(
assert category_mappings.count() == 2
assert category_mappings[0].destination_account.value == "Internet"
assert category_mappings[1].destination_account.value == "Meals"


def test_get_existing_fyle_attributes(
db,
create_temp_workspace,
add_expense_destination_attributes_1,
add_expense_destination_attributes_3,
add_import_settings
):
category = Category(1, 'ACCOUNT', None)

paginated_destination_attributes = DestinationAttribute.objects.filter(workspace_id=1, attribute_type='ACCOUNT')
paginated_destination_attributes_without_duplicates = category.remove_duplicate_attributes(paginated_destination_attributes)
paginated_destination_attribute_values = [attribute.value for attribute in paginated_destination_attributes_without_duplicates]
existing_fyle_attributes_map = category.get_existing_fyle_attributes(paginated_destination_attribute_values)

assert existing_fyle_attributes_map == {'internet': '10091', 'meals': '10092'}

# with code prepending
category.use_code_in_naming = True
paginated_destination_attributes = DestinationAttribute.objects.filter(workspace_id=1, attribute_type='ACCOUNT', code__isnull=False)
paginated_destination_attributes_without_duplicates = category.remove_duplicate_attributes(paginated_destination_attributes)
paginated_destination_attribute_values = [attribute.value for attribute in paginated_destination_attributes_without_duplicates]
existing_fyle_attributes_map = category.get_existing_fyle_attributes(paginated_destination_attribute_values)

assert existing_fyle_attributes_map == {'123 sage300': '10095'}


def test_construct_fyle_payload_with_code(
db,
create_temp_workspace,
add_expense_destination_attributes_1,
add_expense_destination_attributes_3,
add_import_settings
):
category = Category(1, 'ACCOUNT', None, True)

paginated_destination_attributes = DestinationAttribute.objects.filter(workspace_id=1, attribute_type='ACCOUNT')
paginated_destination_attributes_without_duplicates = category.remove_duplicate_attributes(paginated_destination_attributes)
paginated_destination_attribute_values = [attribute.value for attribute in paginated_destination_attributes_without_duplicates]
existing_fyle_attributes_map = category.get_existing_fyle_attributes(paginated_destination_attribute_values)

# already exists
fyle_payload = category.construct_fyle_payload(
paginated_destination_attributes,
existing_fyle_attributes_map,
True
)

assert fyle_payload == []

# create new case
existing_fyle_attributes_map = {}
fyle_payload = category.construct_fyle_payload(
paginated_destination_attributes,
existing_fyle_attributes_map,
True
)

assert fyle_payload == data["create_fyle_category_payload_with_code_create_new_case"]


def test_disable_categories(
db,
mocker,
create_temp_workspace,
add_fyle_credentials,
add_expense_destination_attributes_1,
add_import_settings
):
workspace_id = 1

projects_to_disable = {
'destination_id': {
'value': 'old_category',
'updated_value': 'new_category',
'code': 'old_category_code',
'updated_code': 'old_category_code'
}
}

ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='CATEGORY',
display_name='Category',
value='old_category',
source_id='source_id',
active=True
)

mock_platform = mocker.patch('apps.mappings.imports.modules.categories.PlatformConnector')
bulk_post_call = mocker.patch.object(mock_platform.return_value.categories, 'post_bulk')

disable_categories(workspace_id, projects_to_disable)

assert bulk_post_call.call_count == 1

projects_to_disable = {
'destination_id': {
'value': 'old_category_2',
'updated_value': 'new_category',
'code': 'old_category_code',
'updated_code': 'new_category_code'
}
}

disable_categories(workspace_id, projects_to_disable)
assert bulk_post_call.call_count == 1

# Test disable projects with code in naming
import_settings = ImportSetting.objects.get(workspace_id=workspace_id)
import_settings.import_code_fields = ['ACCOUNT']
import_settings.save()

ExpenseAttribute.objects.create(
workspace_id=workspace_id,
attribute_type='CATEGORY',
display_name='Category',
value='old_category_code old_category',
source_id='source_id_123',
active=True
)

projects_to_disable = {
'destination_id': {
'value': 'old_category',
'updated_value': 'new_category',
'code': 'old_category_code',
'updated_code': 'old_category_code'
}
}

payload = [{
'name': 'old_category_code old_category',
'code': 'destination_id',
'is_enabled': False,
'id': 'source_id_123'
}]

bulk_payload = disable_categories(workspace_id, projects_to_disable)
assert bulk_payload == payload
Loading