diff --git a/apps/sage300/utils.py b/apps/sage300/utils.py index 873575b..5fc2fc8 100644 --- a/apps/sage300/utils.py +++ b/apps/sage300/utils.py @@ -141,15 +141,7 @@ 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 + source_type = self.get_source_type(attribute_type, workspace_id) if is_generator: for data in data_gen: @@ -307,3 +299,22 @@ def sync_cost_categories(self, import_log = None): CostCategory.bulk_create_or_update(categories, self.workspace_id) version.cost_category = latest_version version.save() + + def get_source_type(self, attribute_type, workspace_id): + """ + Get the source type to fetch the disable callback function + :return: Source type + """ + 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 + elif attribute_type == 'ACCOUNT': + source_type = 'CATEGORY' + + return source_type diff --git a/requirements.txt b/requirements.txt index 67a1911..62d0a36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,7 @@ fyle==0.37.0 # Reusable Fyle Packages fyle-rest-auth==1.7.2 -fyle-accounting-mappings==1.34.0 +fyle-accounting-mappings==1.34.1 fyle-integrations-platform-connector==1.39.0 diff --git a/tests/test_mappings/fixtures.py b/tests/test_mappings/fixtures.py index 6d9e9ec..d4e981d 100644 --- a/tests/test_mappings/fixtures.py +++ b/tests/test_mappings/fixtures.py @@ -5718,4 +5718,45 @@ ] } ], + "paginated_destination_attributes_view_response_1": { + "count": 0, + "next": None, + "previous": None, + "results": [] + }, + "paginated_destination_attributes_view_response_2": { + "count": 2, + "next": None, + "previous": None, + "results": [ + { + "id": 3, + "attribute_type": "JOB", + "display_name": "CRE Platform", + "value": "CRE Platform", + "destination_id": "10065", + "auto_created": False, + "active": True, + "detail": "Sage 300 Project - CRE Platform, Id - 10065", + "code": "123", + "created_at": "2024-07-22T16:10:19.130448Z", + "updated_at": "2024-07-22T16:10:19.130456Z", + "workspace": 1 + }, + { + "id": 4, + "attribute_type": "JOB", + "display_name": "Integrations CRE", + "value": "Integrations CRE", + "destination_id": "10082", + "auto_created": False, + "active": True, + "detail": "Sage 300 Project - Integrations CRE, Id - 10082", + "code": "123", + "created_at": "2024-07-22T16:10:19.130823Z", + "updated_at": "2024-07-22T16:10:19.130832Z", + "workspace": 1 + } + ] + } } diff --git a/tests/test_mappings/test_imports/test_modules/test_categories.py b/tests/test_mappings/test_imports/test_modules/test_categories.py index 7fdd9b0..c2528af 100644 --- a/tests/test_mappings/test_imports/test_modules/test_categories.py +++ b/tests/test_mappings/test_imports/test_modules/test_categories.py @@ -156,7 +156,7 @@ def test_disable_categories( ): workspace_id = 1 - projects_to_disable = { + categories_to_disable = { 'destination_id': { 'value': 'old_category', 'updated_value': 'new_category', @@ -177,11 +177,11 @@ def test_disable_categories( 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) + disable_categories(workspace_id, categories_to_disable) assert bulk_post_call.call_count == 1 - projects_to_disable = { + categories_to_disable = { 'destination_id': { 'value': 'old_category_2', 'updated_value': 'new_category', @@ -190,7 +190,7 @@ def test_disable_categories( } } - disable_categories(workspace_id, projects_to_disable) + disable_categories(workspace_id, categories_to_disable) assert bulk_post_call.call_count == 1 # Test disable projects with code in naming @@ -207,7 +207,7 @@ def test_disable_categories( active=True ) - projects_to_disable = { + categories_to_disable = { 'destination_id': { 'value': 'old_category', 'updated_value': 'new_category', @@ -223,5 +223,5 @@ def test_disable_categories( 'id': 'source_id_123' }] - bulk_payload = disable_categories(workspace_id, projects_to_disable) + bulk_payload = disable_categories(workspace_id, categories_to_disable) assert bulk_payload == payload diff --git a/tests/test_mappings/test_imports/test_modules/test_merchants.py b/tests/test_mappings/test_imports/test_modules/test_merchants.py index f50507d..a348912 100644 --- a/tests/test_mappings/test_imports/test_modules/test_merchants.py +++ b/tests/test_mappings/test_imports/test_modules/test_merchants.py @@ -134,7 +134,7 @@ def test_disable_merchants( ): workspace_id = 1 - projects_to_disable = { + merchants_to_disable = { 'destination_id': { 'value': 'old_merchant', 'updated_value': 'new_merchant', @@ -155,11 +155,11 @@ def test_disable_merchants( mock_platform = mocker.patch('apps.mappings.imports.modules.merchants.PlatformConnector') bulk_post_call = mocker.patch.object(mock_platform.return_value.merchants, 'post') - disable_merchants(workspace_id, projects_to_disable) + disable_merchants(workspace_id, merchants_to_disable) assert bulk_post_call.call_count == 1 - projects_to_disable = { + merchants_to_disable = { 'destination_id': { 'value': 'old_merchant_2', 'updated_value': 'new_merchant', @@ -168,7 +168,7 @@ def test_disable_merchants( } } - disable_merchants(workspace_id, projects_to_disable) + disable_merchants(workspace_id, merchants_to_disable) assert bulk_post_call.call_count == 1 # Test disable projects with code in naming @@ -185,7 +185,7 @@ def test_disable_merchants( active=True ) - projects_to_disable = { + merchants_to_disable = { 'destination_id': { 'value': 'old_merchant', 'updated_value': 'new_merchant', @@ -196,5 +196,5 @@ def test_disable_merchants( payload = ['old_merchant_code old_merchant'] - bulk_payload = disable_merchants(workspace_id, projects_to_disable) + bulk_payload = disable_merchants(workspace_id, merchants_to_disable) assert bulk_payload[0] == payload[0] diff --git a/tests/test_mappings/test_imports/test_modules/test_projects.py b/tests/test_mappings/test_imports/test_modules/test_projects.py index b49ef2d..5487c14 100644 --- a/tests/test_mappings/test_imports/test_modules/test_projects.py +++ b/tests/test_mappings/test_imports/test_modules/test_projects.py @@ -1,6 +1,7 @@ from apps.mappings.imports.modules.projects import Project from fyle_accounting_mappings.models import DestinationAttribute from .fixtures import data +from tests.helper import dict_compare_keys def test_construct_fyle_payload(api_client, test_connection, mocker, create_temp_workspace, add_cost_category, add_sage300_creds, add_fyle_credentials, add_project_mappings): @@ -56,7 +57,7 @@ def test_construct_fyle_payload(api_client, test_connection, mocker, create_temp True ) - assert fyle_payload == data['create_fyle_project_payload_create_disable_case2'] + assert dict_compare_keys(fyle_payload, data['create_fyle_project_payload_create_disable_case2']) == [], 'create fyle project payload create disable case2 return diffs in keys' def test_get_existing_fyle_attributes(db, create_temp_workspace, add_project_mappings, add_import_settings): diff --git a/tests/test_mappings/test_views.py b/tests/test_mappings/test_views.py new file mode 100644 index 0000000..76eec2a --- /dev/null +++ b/tests/test_mappings/test_views.py @@ -0,0 +1,47 @@ +from django.urls import reverse +from rest_framework import status +from tests.test_mappings.fixtures import data +from tests.helper import dict_compare_keys + + +def test_paginated_destination_attributes_view(db, api_client, test_connection, create_temp_workspace, add_project_mappings): + """ + Test paginated destination attributes view + """ + workspace_id = 1 + # url = f'/api/workspaces/{workspace_id}/mappings/paginated_destination_attributes/?limit=100&offset=0&attribute_type={attribute_type}&active=true&value={value}' + + url = reverse('paginated_destination_attributes_view', args=[workspace_id]) + + # Test with no value matching the data + params = { + 'limit': 100, + 'offset': 0, + 'attribute_type': 'JOB', + 'active': 'true', + 'value': 'Something not in the data' + } + + api_client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(test_connection.access_token)) + + response = api_client.get(url, params) + assert response.status_code == status.HTTP_200_OK + assert dict_compare_keys(response.json(), data['paginated_destination_attributes_view_response_1']) == [], 'paginated destination attributes view api return diffs in keys' + + # Test with value (code) in the data + params.update(value='123') + + api_client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(test_connection.access_token)) + + response = api_client.get(url, params) + assert response.status_code == status.HTTP_200_OK + assert dict_compare_keys(response.json(), data['paginated_destination_attributes_view_response_2']) == [], 'paginated destination attributes view api return diffs in keys' + + # Test with value (name) in the data + params.update(value='cre') + + api_client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(test_connection.access_token)) + + response = api_client.get(url, params) + assert response.status_code == status.HTTP_200_OK + assert dict_compare_keys(response.json(), data['paginated_destination_attributes_view_response_2']) == [], 'paginated destination attributes view api return diffs in keys' diff --git a/tests/test_sage300/test_utils.py b/tests/test_sage300/test_utils.py index ffc1db9..c4616e3 100644 --- a/tests/test_sage300/test_utils.py +++ b/tests/test_sage300/test_utils.py @@ -1,8 +1,11 @@ +import pytest +from unittest.mock import MagicMock from apps.sage300.utils import SageDesktopConnector, Sage300Credential from fyle_accounting_mappings.models import DestinationAttribute from apps.mappings.models import Version from apps.workspaces.models import Workspace from apps.mappings.models import ImportLog +from sage_desktop_sdk.core.schema.read_only import CommitmentItem def test_sage_desktop_connector( @@ -639,3 +642,107 @@ def test_bulk_create_or_update_destination_attributes_without_code(db, create_te # Verify update assert DestinationAttribute.objects.get(destination_id='DestID1').value == 'Updated Value 1' assert DestinationAttribute.objects.get(destination_id='DestID2').value == 'Updated Value 2' + + +@pytest.fixture +def sync_instance( + db, + mocker, + create_temp_workspace, + add_sage300_creds +): + workspace_id = 1 + sage_creds = Sage300Credential.objects.get(workspace_id=workspace_id) + + mocker.patch('apps.sage300.utils.SageDesktopSDK') + + sage_connector = SageDesktopConnector( + credentials_object=sage_creds, + workspace_id=workspace_id + ) + + return sage_connector + + +def test_sync_data_with_generator(sync_instance, mocker): + # Mock dependencies + mock_mapping_setting = mocker.patch('apps.sage300.utils.MappingSetting') + mock_bulk_create = mocker.patch('apps.sage300.utils.DestinationAttribute.bulk_create_or_update_destination_attributes') + mock_get_attribute_class = mocker.patch('apps.sage300.utils.SageDesktopConnector._get_attribute_class') + mock_update_latest_version = mocker.patch('apps.sage300.utils.SageDesktopConnector._update_latest_version') + mock_import_string = mocker.patch('apps.sage300.utils.import_string') + + # Setup mock return values + mock_mapping_setting.objects.filter.return_value.first.return_value = MagicMock(is_custom=False, source_field='PROJECT', destination_field='JOB') + mock_get_attribute_class.return_value = 'Job' + mock_update_latest_version.return_value = None + mock_import_string.return_value.from_dict.return_value = MagicMock() + + # Mock data generator + data_gen = iter([iter([{'key': 'value'}])]) + + # Call method + sync_instance._sync_data(data_gen, 'JOB', 'job', 1, ['code', 'version'], is_generator=True) + + # Assertions + mock_bulk_create.assert_called_once() + called_args = mock_bulk_create.call_args[0] + + assert called_args[1] == 'JOB' + assert called_args[2] == 1 + assert mock_bulk_create.call_args[1]['attribute_disable_callback_path'] == 'apps.sage300.helpers.disable_projects' # ATTRIBUTE_CALLBACK_MAP['PROJECT'] + + +def test_sync_data_without_generator(sync_instance, mocker): + # Mock dependencies + mock_bulk_create = mocker.patch('apps.sage300.utils.DestinationAttribute.bulk_create_or_update_destination_attributes') + mock_get_attribute_class = mocker.patch('apps.sage300.utils.SageDesktopConnector._get_attribute_class') + mock_update_latest_version = mocker.patch('apps.sage300.utils.SageDesktopConnector._update_latest_version') + mock_import_string = mocker.patch('apps.sage300.utils.import_string') + + # Setup mock return values + mock_get_attribute_class.return_value = 'CommitmentItem' + mock_update_latest_version.return_value = None + mock_import_string.return_value.from_dict.return_value = MagicMock() + + # Mock data + data = [ + CommitmentItem( + id='ffb326e3-783f-4667-a443-b06c0083ef07', + version=212585, + amount=250.0, + amount_approved=0.0, + amount_invoiced=591.0, + amount_paid=0.0, + amount_original=250.0, + amount_pending=0.0, + amount_retained=22.5, + category_id='ece00064-b585-4f87-b0bc-b06100a9bec8', + code='1', + commitment_id='ddb74931-f138-4e2e-a1f6-b06c0083edd5', + cost_code_id='d3b321be-1e6c-4d4b-add4-b06100a9bd2c', + created_on_utc='2023-08-28T08:00:21Z', + description='', + has_external_id=True, + is_active=True, + is_archived=False, + job_id='5e0eb476-b189-4409-b9b3-b061009602a4', + name='Refrigeration', + standard_category_id='302918fb-2f89-4d7f-972a-b05b00f3c431', + tax=0.0, + tax_group_id='a721a071-9cac-4134-9d8b-b05b00f3cb2a', + tax_group_code='EXMPT', + unit_cost=0.0, + units=0.0 + ) + ] + + # Call method + sync_instance._sync_data(data, 'COMMITMENT_ITEM', 'commitment_item', 1, ['code', 'version'], is_generator=False) + + # Assertions + mock_bulk_create.assert_called_once() + called_args = mock_bulk_create.call_args[0] + assert called_args[1] == 'COMMITMENT_ITEM' + assert called_args[2] == 1 + assert len(called_args) == 4