From 13abf5ac6a6ce1c6ddde44b2c8e6ec5563718a49 Mon Sep 17 00:00:00 2001 From: Ashutosh singh <55102089+Ashutosh619-sudo@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:02:31 +0530 Subject: [PATCH] Update destination attributes if internal id has changed (#626) * Update if internal id has changed * resolving unique constraint issue * changing variable name * comment resolved * Test for update_destination_attributes function --- apps/netsuite/connector.py | 51 ++++++++++++++++++++ tests/test_netsuite/fixtures.py | 69 +++++++++++++++++++++++++++ tests/test_netsuite/test_connector.py | 40 +++++++++++++++- 3 files changed, 159 insertions(+), 1 deletion(-) diff --git a/apps/netsuite/connector.py b/apps/netsuite/connector.py index d1ad30de..7ba48509 100644 --- a/apps/netsuite/connector.py +++ b/apps/netsuite/connector.py @@ -364,11 +364,62 @@ def get_custom_segment_attributes(self, attribute_type: str, internal_id: str): ) return custom_segment_attributes + + def update_destination_attributes(self, attribute_type:str, custom_records: List): + """ + Sometime custom_attributes internal_id change due to some reason + we update destination_attributes accordingly. + """ + changed_destination_attributes = [] + custom_segment_attributes = [] + + for field in custom_records: + custom_segment_attributes.append( + { + 'attribute_type': attribute_type, + 'display_name': custom_records[0]['recType']['name'], + 'value': field['name'], + 'destination_id': field['internalId'], + 'active': not field['isInactive'] + } + ) + + for custom_segment_attribute in custom_segment_attributes: + existing = DestinationAttribute.objects.filter( + attribute_type=custom_segment_attribute['attribute_type'], + value=custom_segment_attribute['value'], + workspace_id=self.workspace_id + ).first() + if existing and existing.destination_id != custom_segment_attribute['destination_id']: + changed_destination_attributes.append((existing.id, existing.destination_id, custom_segment_attribute['destination_id'])) + + if not changed_destination_attributes: + return + + temp_internal_id_base = 'temp_id' + temp_internal_ids = {} + destination_attributes_to_be_updated = [] + + for i, (record_id, old_id, new_id) in enumerate(changed_destination_attributes): + temp_id = f"{temp_internal_id_base}_{i}" + temp_internal_ids[record_id] = (temp_id, new_id) + destination_attributes_to_be_updated.append(DestinationAttribute(id=record_id, destination_id=temp_id, workspace_id=self.workspace_id)) + + DestinationAttribute.objects.bulk_update(destination_attributes_to_be_updated, ['destination_id'], batch_size=100) + + updated_destination_attributes = [ + DestinationAttribute(id=record_id, destination_id=new_id, workspace_id=self.workspace_id) + for record_id, (temp_id, new_id) in temp_internal_ids.items() + ] + + DestinationAttribute.objects.bulk_update(updated_destination_attributes, ['destination_id'], batch_size=100) def get_custom_record_attributes(self, attribute_type: str, internal_id: str): custom_segment_attributes = [] custom_records = self.connection.custom_record_types.get_all_by_id(internal_id) + self.update_destination_attributes(attribute_type=attribute_type, custom_records=custom_records) + # Get all the current destination attributes destination_attributes = DestinationAttribute.objects.filter( workspace_id=self.workspace_id, diff --git a/tests/test_netsuite/fixtures.py b/tests/test_netsuite/fixtures.py index bd3c2da5..5482a485 100644 --- a/tests/test_netsuite/fixtures.py +++ b/tests/test_netsuite/fixtures.py @@ -1696,6 +1696,75 @@ 'externalId': 'report 1256 - ashwin.t@fyle.in' } ], + + 'custom_records': [ + { + 'recType': {'name': 'custom_type'}, + 'name': 'Type A', + 'internalId': '1', + 'isInactive': False + }, + { + 'recType': {'name': 'custom_type'}, + 'name': 'Type B', + 'internalId': '22', # Changed from '2' to '22' + 'isInactive': False + }, + { + 'recType': {'name': 'custom_type'}, + 'name': 'Type C', + 'internalId': '33', # Changed from '3' to '33' + 'isInactive': True + }, + { + 'recType': {'name': 'custom_type'}, + 'name': 'Type D', + 'internalId': '4', + 'isInactive': False + } + ], + 'custom_segment_destination_attributes':[ + { + 'attribute_type': 'CUSTOM_TYPE', + 'display_name': 'custom_type', + 'value': 'Type A', + 'destination_id': '1', + 'auto_created': False, + 'active': True, + 'detail': {}, + 'workspace_id': 1, + }, + { + 'attribute_type': 'CUSTOM_TYPE', + 'display_name': 'custom_type', + 'value': 'Type B', + 'destination_id': '2', + 'auto_created': False, + 'active': True, + 'detail': {}, + 'workspace_id': 1, + }, + { + 'attribute_type': 'CUSTOM_TYPE', + 'display_name': 'custom_type', + 'value': 'Type C', + 'destination_id': '3', + 'auto_created': False, + 'active': True, + 'detail': {}, + 'workspace_id': 1, + }, + { + 'attribute_type': 'CUSTOM_TYPE', + 'display_name': 'custom_type', + 'value': 'Type D', + 'destination_id': '4', + 'auto_created': False, + 'active': True, + 'detail': {}, + 'workspace_id': 1, + }, + ], "destination_attributes": [ { 'id': 6, diff --git a/tests/test_netsuite/test_connector.py b/tests/test_netsuite/test_connector.py index b3b25117..3c105413 100644 --- a/tests/test_netsuite/test_connector.py +++ b/tests/test_netsuite/test_connector.py @@ -3,7 +3,7 @@ from apps.fyle.models import ExpenseGroup from fyle_accounting_mappings.models import DestinationAttribute, ExpenseAttribute from apps.netsuite.connector import NetSuiteConnector, NetSuiteCredentials -from apps.workspaces.models import Configuration +from apps.workspaces.models import Configuration, Workspace from netsuitesdk import NetSuiteRequestError from tests.helper import dict_compare_keys from .fixtures import data @@ -621,3 +621,41 @@ def test_post_journal_entry_exception(db, mocker, create_journal_entry): with mock.patch('netsuitesdk.api.journal_entries.JournalEntries.post') as mock_call: mock_call.side_effect = [NetSuiteRequestError('An error occured in a upsert request: The transaction date you specified is not within the date range of your accounting period.'), None] netsuite_connection.post_journal_entry(journal_entry_transaction, journal_entry_transaction_lineitems) + +def test_update_destination_attributes(db, mocker): + mocker.patch( + 'netsuitesdk.api.custom_record_types.CustomRecordTypes.get_all_by_id', + return_value=data['custom_records'] + ) + + custom_segments = data['custom_segment_destination_attributes'] + custom_segments_destination_attribute = [] + for custom_segment in custom_segments: + workspace_id = custom_segment.pop('workspace_id') + workspace = Workspace.objects.get(id=workspace_id) + custom_segments_destination_attribute.append( + DestinationAttribute( + **custom_segment, + workspace=workspace + ) + ) + DestinationAttribute.objects.bulk_create(custom_segments_destination_attribute, batch_size=50) + workspace_id = 1 + + netsuite_credentials = NetSuiteCredentials.objects.get(workspace_id=workspace_id) + netsuite_connection = NetSuiteConnector(netsuite_credentials=netsuite_credentials, workspace_id=workspace_id) + + custom_records = netsuite_connection.connection.custom_record_types.get_all_by_id('1') + netsuite_connection.update_destination_attributes('CUSTOM_TYPE', custom_records) + + custom_type_destination_attributes = DestinationAttribute.objects.filter(attribute_type='CUSTOM_TYPE', workspace_id=1) + + for custom_type_destination_attribute in custom_type_destination_attributes: + if custom_type_destination_attribute.value == 'Type B': + assert custom_type_destination_attribute.destination_id == '22' + elif custom_type_destination_attribute.value == 'Type C': + assert custom_type_destination_attribute.destination_id == '33' + elif custom_type_destination_attribute.value == 'Type A': + assert custom_type_destination_attribute.destination_id == '1' + elif custom_type_destination_attribute.value == 'Type D': + assert custom_type_destination_attribute.destination_id == '4'