diff --git a/apps/mappings/queues.py b/apps/mappings/queues.py index 705fddb1..3f044119 100644 --- a/apps/mappings/queues.py +++ b/apps/mappings/queues.py @@ -25,16 +25,6 @@ def async_auto_create_expense_field_mapping(mapping_setting: MappingSetting): async_task('apps.mappings.tasks.auto_create_expense_fields_mappings', int(mapping_setting.workspace_id), mapping_setting.destination_field, mapping_setting.source_field) -def schedule_cost_centers_creation(import_to_fyle, workspace_id): - if import_to_fyle: - schedule, _ = Schedule.objects.update_or_create(func='apps.mappings.tasks.auto_create_cost_center_mappings', args='{}'.format(workspace_id), defaults={'schedule_type': Schedule.MINUTES, 'minutes': 24 * 60, 'next_run': datetime.now()}) - else: - schedule: Schedule = Schedule.objects.filter(func='apps.mappings.tasks.auto_create_cost_center_mappings', args='{}'.format(workspace_id)).first() - - if schedule: - schedule.delete() - - def schedule_fyle_attributes_creation(workspace_id: int): mapping_settings = MappingSetting.objects.filter(is_custom=True, import_to_fyle=True, workspace_id=workspace_id).all() if mapping_settings: @@ -137,7 +127,7 @@ def construct_tasks_and_chain_import_fields_to_fyle(workspace_id): # For now we are only adding PROJECTS support that is why we are hardcoding it if mapping_settings: for mapping_setting in mapping_settings: - if mapping_setting.source_field in ['PROJECT']: + if mapping_setting.source_field in ['PROJECT', 'COST_CENTER']: task_settings['mapping_settings'].append({ 'source_field': mapping_setting.source_field, 'destination_field': mapping_setting.destination_field, diff --git a/apps/mappings/schedules.py b/apps/mappings/schedules.py index c90b0cda..162969cd 100644 --- a/apps/mappings/schedules.py +++ b/apps/mappings/schedules.py @@ -31,7 +31,7 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe import_fields_count = MappingSetting.objects.filter( import_to_fyle=True, workspace_id=workspace_general_settings.workspace_id, - source_field__in=['PROJECT'] + source_field__in=['PROJECT', 'COST_CENTER'] ).count() # If the import fields count is 0, delete the schedule diff --git a/apps/mappings/signals.py b/apps/mappings/signals.py index 3cbb4cc0..974b5bfe 100644 --- a/apps/mappings/signals.py +++ b/apps/mappings/signals.py @@ -7,7 +7,6 @@ from apps.mappings.queues import ( async_auto_create_expense_field_mapping, - schedule_cost_centers_creation, schedule_fyle_attributes_creation, ) from apps.mappings.tasks import upload_attributes_to_fyle @@ -51,12 +50,9 @@ def run_post_mapping_settings_triggers(sender, instance: MappingSetting, **kwarg """ workspace_general_settings = WorkspaceGeneralSettings.objects.filter(workspace_id=instance.workspace_id).first() - if instance.source_field == 'PROJECT': + if instance.source_field in ['PROJECT', 'COST_CENTER']: new_schedule_or_delete_fyle_import_tasks(workspace_general_settings, instance) - if instance.source_field == 'COST_CENTER': - schedule_cost_centers_creation(instance.import_to_fyle, int(instance.workspace_id)) - if instance.is_custom: schedule_fyle_attributes_creation(int(instance.workspace_id)) diff --git a/apps/mappings/tasks.py b/apps/mappings/tasks.py index c2ad6938..0406ed9a 100644 --- a/apps/mappings/tasks.py +++ b/apps/mappings/tasks.py @@ -359,24 +359,6 @@ def sync_qbo_attribute(qbo_attribute_type: str, workspace_id: int): qbo_connection.sync_vendors() -def create_fyle_cost_centers_payload(qbo_attributes: List[DestinationAttribute], existing_fyle_cost_centers: list): - """ - Create Fyle Cost Centers Payload from QBO Objects - :param existing_fyle_cost_centers: Existing cost center names - :param qbo_attributes: QBO Objects - :return: Fyle Cost Centers Payload - """ - fyle_cost_centers_payload = [] - - for qbo_attribute in qbo_attributes: - if qbo_attribute.value not in existing_fyle_cost_centers: - fyle_cost_centers_payload.append( - {'name': qbo_attribute.value, 'is_enabled': True if qbo_attribute.active is None else qbo_attribute.active, 'description': 'Cost Center - {0}, Id - {1}'.format(qbo_attribute.value, qbo_attribute.destination_id)} - ) - - return fyle_cost_centers_payload - - def create_fyle_tax_group_payload(qbo_attributes: List[DestinationAttribute], existing_fyle_tax_groups: list): """ Create Fyle Cost Centers Payload from QBO Objects @@ -393,46 +375,6 @@ def create_fyle_tax_group_payload(qbo_attributes: List[DestinationAttribute], ex return fyle_tax_group_payload -def post_cost_centers_in_batches(platform: PlatformConnector, workspace_id: int, qbo_attribute_type: str): - existing_cost_center_names = ExpenseAttribute.objects.filter(attribute_type='COST_CENTER', workspace_id=workspace_id).values_list('value', flat=True) - - qbo_attributes_count = DestinationAttribute.objects.filter(attribute_type=qbo_attribute_type, workspace_id=workspace_id).count() - - page_size = 200 - - for offset in range(0, qbo_attributes_count, page_size): - limit = offset + page_size - paginated_qbo_attributes = DestinationAttribute.objects.filter(attribute_type=qbo_attribute_type, workspace_id=workspace_id).order_by('value', 'id')[offset:limit] - - paginated_qbo_attributes = remove_duplicates(paginated_qbo_attributes) - - fyle_payload: List[Dict] = create_fyle_cost_centers_payload(paginated_qbo_attributes, existing_cost_center_names) - - if fyle_payload: - platform.cost_centers.post_bulk(fyle_payload) - platform.cost_centers.sync() - - Mapping.bulk_create_mappings(paginated_qbo_attributes, 'COST_CENTER', qbo_attribute_type, workspace_id) - - -@handle_import_exceptions(task_name='Auto Create Cost Center Mappings') -def auto_create_cost_center_mappings(workspace_id): - """ - Create Cost Center Mappings - """ - fyle_credentials: FyleCredential = FyleCredential.objects.get(workspace_id=workspace_id) - - platform = PlatformConnector(fyle_credentials) - - mapping_setting = MappingSetting.objects.get(source_field='COST_CENTER', import_to_fyle=True, workspace_id=workspace_id) - - platform.cost_centers.sync() - - sync_qbo_attribute(mapping_setting.destination_field, workspace_id) - - post_cost_centers_in_batches(platform, workspace_id, mapping_setting.destination_field) - - def create_fyle_expense_custom_field_payload(qbo_attributes: List[DestinationAttribute], workspace_id: int, fyle_attribute: str, platform: PlatformConnector, source_placeholder: str = None): """ Create Fyle Expense Custom Field Payload from QBO Objects diff --git a/apps/quickbooks_online/actions.py b/apps/quickbooks_online/actions.py index af6aad51..e08c4441 100644 --- a/apps/quickbooks_online/actions.py +++ b/apps/quickbooks_online/actions.py @@ -14,7 +14,9 @@ from apps.fyle.models import ExpenseGroup from apps.fyle.actions import update_complete_expenses from apps.quickbooks_online.utils import QBOConnector +from apps.mappings.helpers import get_auto_sync_permission from apps.tasks.models import TaskLog +from apps.workspaces.models import WorkspaceGeneralSettings from apps.workspaces.models import LastExportDetail, QBOCredential, Workspace from .helpers import generate_export_type_and_id @@ -24,6 +26,17 @@ logger.level = logging.INFO +SYNC_METHODS = { + 'ACCOUNT': 'accounts', + 'ITEM': 'items', + 'VENDOR': 'vendors', + 'DEPARTMENT': 'departments', + 'TAX_CODE': 'tax_codes', + 'CLASS': 'classes', + 'CUSTOMER': 'customers', +} + + def update_last_export_details(workspace_id): last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id) @@ -63,13 +76,26 @@ def refresh_quickbooks_dimensions(workspace_id: int): quickbooks_connector = QBOConnector(quickbooks_credentials, workspace_id=workspace_id) mapping_settings = MappingSetting.objects.filter(workspace_id=workspace_id, import_to_fyle=True) + credentials = QBOCredential.objects.get(workspace_id=workspace_id) + workspace_general_settings = WorkspaceGeneralSettings.objects.get(workspace_id=workspace_id) + chain = Chain() for mapping_setting in mapping_settings: - if mapping_setting.source_field == 'PROJECT': - chain.append('apps.mappings.tasks.auto_import_and_map_fyle_fields', int(workspace_id)) - elif mapping_setting.source_field == 'COST_CENTER': - chain.append('apps.mappings.tasks.auto_create_cost_center_mappings', int(workspace_id)) + if mapping_setting.source_field in ['PROJECT', 'COST_CENTER']: + chain.append( + 'fyle_integrations_imports.tasks.trigger_import_via_schedule', + workspace_id, + mapping_setting.destination_field, + mapping_setting.source_field, + 'apps.quickbooks_online.utils.QBOConnector', + credentials, + [SYNC_METHODS[mapping_setting.destination_field]], + get_auto_sync_permission(workspace_general_settings, mapping_setting), + False, + None, + False + ) elif mapping_setting.is_custom: chain.append('apps.mappings.tasks.async_auto_create_custom_field_mappings', int(workspace_id)) diff --git a/apps/workspaces/apis/import_settings/triggers.py b/apps/workspaces/apis/import_settings/triggers.py index 1f7b5cc8..d9c49d13 100644 --- a/apps/workspaces/apis/import_settings/triggers.py +++ b/apps/workspaces/apis/import_settings/triggers.py @@ -6,7 +6,6 @@ from apps.fyle.models import ExpenseGroupSettings from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks from apps.mappings.queues import ( - schedule_cost_centers_creation, schedule_fyle_attributes_creation, schedule_tax_groups_creation, ) @@ -100,15 +99,6 @@ def pre_save_mapping_settings(self): """ mapping_settings = self.__mapping_settings - cost_center_mapping_available = False - - for setting in mapping_settings: - if setting['source_field'] == 'COST_CENTER': - cost_center_mapping_available = True - - if not cost_center_mapping_available: - schedule_cost_centers_creation(False, self.__workspace_id) - schedule_fyle_attributes_creation(self.__workspace_id) # Removal of department grouping will be taken care from post_delete() signal diff --git a/fyle_integrations_imports b/fyle_integrations_imports index bd1d09d2..63d18ffe 160000 --- a/fyle_integrations_imports +++ b/fyle_integrations_imports @@ -1 +1 @@ -Subproject commit bd1d09d2d6606d033245c890297aa39e5cea8302 +Subproject commit 63d18ffeb56380a18e547e9b32de4c3aa7440494 diff --git a/scripts/python/create-update-new-categories-import.py b/scripts/python/create-update-new-categories-import.py index 4f8a18df..dafebfca 100644 --- a/scripts/python/create-update-new-categories-import.py +++ b/scripts/python/create-update-new-categories-import.py @@ -9,8 +9,6 @@ try: # Create/update new schedules in a transaction block with transaction.atomic(): - deleted_count = 0 - updated_count = 0 for schedule in existing_import_enabled_schedules: random_number = random.randint(1, 23) configuration = WorkspaceGeneralSettings.objects.get(workspace_id=schedule['args']) @@ -29,5 +27,7 @@ minutes=24 * 60, next_run=datetime.now() + timedelta(hours=random_number) ) + # remove this sanity check after running this script + raise Exception("This is a sanity check") except Exception as e: print(e) diff --git a/scripts/python/create-update-new-cost-centers-import.py b/scripts/python/create-update-new-cost-centers-import.py new file mode 100644 index 00000000..e9fb0ebd --- /dev/null +++ b/scripts/python/create-update-new-cost-centers-import.py @@ -0,0 +1,43 @@ +from django.db import transaction +import random +from datetime import datetime, timedelta +from django_q.models import Schedule +from fyle_accounting_mappings.models import MappingSetting + +existing_import_enabled_schedules = Schedule.objects.filter( + func__in=['apps.mappings.tasks.auto_create_cost_center_mappings'] +).values('args') + +try: + with transaction.atomic(): + for schedule in existing_import_enabled_schedules: + random_number = random.randint(1, 23) + mapping_setting = MappingSetting.objects.filter(source_field='COST_CENTER', workspace_id=schedule['args'], import_to_fyle=True).first() + if mapping_setting: + print('Creating schedule for workspace_id: ', schedule['args']) + # adding the new schedule + Schedule.objects.update_or_create( + func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle', + args=schedule['args'], + defaults={ + 'schedule_type': Schedule.MINUTES, + 'minutes':24 * 60, + 'next_run':datetime.now() + timedelta(hours=random_number) + } + ) + # deleting the old schedule + Schedule.objects.filter( + func='apps.mappings.tasks.auto_create_cost_center_mappings', + args=schedule['args'] + ).delete() + # remove this sanity check after running this script + raise Exception("This is a sanity check") +except Exception as e: + print(e) + + +# Run this in sql +# select * from django_q_schedule where func = 'apps.mappings.tasks.auto_create_cost_center_mappings'; +# --rows should be 0 +# If not check the workspace_id and delete the row +# delete from django_q_schedule where func = 'apps.mappings.tasks.auto_create_cost_center_mappings' and args = 'workspace_id'; diff --git a/tests/test_fyle_integrations_imports/test_modules/fixtures.py b/tests/test_fyle_integrations_imports/test_modules/fixtures.py index c3254ee5..ba1dcb2d 100644 --- a/tests/test_fyle_integrations_imports/test_modules/fixtures.py +++ b/tests/test_fyle_integrations_imports/test_modules/fixtures.py @@ -7564,3 +7564,416 @@ } ] } + +cost_center_data = { + "create_new_auto_create_cost_centers_destination_attributes": [ + { + "FullyQualifiedName": "France", + "domain": "QBO", + "Name": "France", + "SyncToken": "0", + "SubClass": False, + "sparse": False, + "Active": True, + "Id": "5000000000000007280", + "MetaData": {"CreateTime": "2015-07-22T13:57:27-07:00", "LastUpdatedTime": "2015-07-22T13:57:27-07:00"}, + }, + { + "FullyQualifiedName": "Denmark", + "domain": "QBO", + "Name": "Denmark", + "SyncToken": "0", + "SubClass": False, + "sparse": False, + "Active": True, + "Id": "9", + "MetaData": {"CreateTime": "2015-07-22T13:57:27-07:00", "LastUpdatedTime": "2015-07-22T13:57:27-07:00"}, + }, + ], + "create_new_auto_create_cost_centers_destination_attributes_subsequent_run": [ + { + "FullyQualifiedName": "France", + "domain": "QBO", + "Name": "France", + "SyncToken": "0", + "SubClass": False, + "sparse": False, + "Active": True, + "Id": "5000000000000007280", + "MetaData": {"CreateTime": "2015-07-22T13:57:27-07:00", "LastUpdatedTime": "2015-07-22T13:57:27-07:00"}, + }, + { + "FullyQualifiedName": "Denmark", + "domain": "QBO", + "Name": "Denmark", + "SyncToken": "0", + "SubClass": False, + "sparse": False, + "Active": True, + "Id": "9", + "MetaData": {"CreateTime": "2015-07-22T13:57:27-07:00", "LastUpdatedTime": "2015-07-22T13:57:27-07:00"}, + }, + { + "FullyQualifiedName": "Australia", + "domain": "QBO", + "Name": "Australia", + "SyncToken": "0", + "SubClass": False, + "sparse": False, + "Active": True, + "Id": "5000000000000007282", + "MetaData": {"CreateTime": "2015-07-22T13:57:27-07:00", "LastUpdatedTime": "2015-07-22T13:57:27-07:00"}, + }, + { + "FullyQualifiedName": "Poland", + "domain": "QBO", + "Name": "Poland", + "SyncToken": "0", + "SubClass": False, + "sparse": False, + "Active": True, + "Id": "10", + "MetaData": {"CreateTime": "2015-07-22T13:57:27-07:00", "LastUpdatedTime": "2015-07-22T13:57:27-07:00"}, + }, + ], + 'create_new_auto_create_cost_centers_expense_attributes_0': [ + { + 'count':6, + 'data':[ + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Enterprise, Id - 600', + 'id':19728, + 'is_enabled':True, + 'name':'Enterprise', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Midsize Business, Id - 500', + 'id':19730, + 'is_enabled':True, + 'name':'Midsize Business', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 1, Id - 100', + 'id':19731, + 'is_enabled':True, + 'name':'Service Line 1', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 2, Id - 200', + 'id':19732, + 'is_enabled':True, + 'name':'Service Line 2', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 3, Id - 300', + 'id':19733, + 'is_enabled':True, + 'name':'Service Line 3', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Small Business, Id - 400', + 'id':19734, + 'is_enabled':True, + 'name':'Small Business', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + } + ], + 'offset':0 + } + ], + 'create_new_auto_create_cost_centers_expense_attributes_1': [ + { + 'count':8, + 'data':[ + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Enterprise, Id - 600', + 'id':19728, + 'is_enabled':True, + 'name':'Enterprise', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Midsize Business, Id - 500', + 'id':19730, + 'is_enabled':True, + 'name':'Midsize Business', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 1, Id - 100', + 'id':19731, + 'is_enabled':True, + 'name':'Service Line 1', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 2, Id - 200', + 'id':19732, + 'is_enabled':True, + 'name':'Service Line 2', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 3, Id - 300', + 'id':19733, + 'is_enabled':True, + 'name':'Service Line 3', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Small Business, Id - 400', + 'id':19734, + 'is_enabled':True, + 'name':'Small Business', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Denmark, Id - 4002', + 'id':197341, + 'is_enabled':True, + 'name':'Denmark', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - France, Id - 4001', + 'id':197342, + 'is_enabled':True, + 'name':'France', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + } + ], + 'offset':0 + } + ], + 'create_new_auto_create_cost_centers_expense_attributes_2': [ + { + 'count':10, + 'data':[ + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Enterprise, Id - 600', + 'id':19728, + 'is_enabled':True, + 'name':'Enterprise', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Midsize Business, Id - 500', + 'id':19730, + 'is_enabled':True, + 'name':'Midsize Business', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 1, Id - 100', + 'id':19731, + 'is_enabled':True, + 'name':'Service Line 1', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 2, Id - 200', + 'id':19732, + 'is_enabled':True, + 'name':'Service Line 2', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Service Line 3, Id - 300', + 'id':19733, + 'is_enabled':True, + 'name':'Service Line 3', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Small Business, Id - 400', + 'id':19734, + 'is_enabled':True, + 'name':'Small Business', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Denmark, Id - 4002', + 'id':197341, + 'is_enabled':True, + 'name':'Denmark', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - France, Id - 4001', + 'id':197342, + 'is_enabled':True, + 'name':'France', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Poland, Id - 4002', + 'id':197343, + 'is_enabled':True, + 'name':'Poland', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + }, + { + 'code':None, + 'created_at':'2023-09-15T06:06:10.923262+00:00', + 'description':'Cost Center - Australia, Id - 4001', + 'id':197344, + 'is_enabled':True, + 'name':'Australia', + 'org_id':'or5qYLrvnoF9', + 'restricted_spender_user_ids':None, + 'updated_at':'2023-09-15T06:06:10.923262+00:00' + } + ], + 'offset':0 + } + ], + "create_fyle_cost_centers_payload_create_new_case":[ + { + "name": "Adidas", + "is_enabled": True, + "description": "Cost Center - Adidas, Id - 5100000000000030664", + }, + { + "name": "cc1", + "is_enabled": True, + "description": "Cost Center - cc1, Id - 5100000000000030665", + }, + { + "name": "cc2", + "is_enabled": True, + "description": "Cost Center - cc2, Id - 5100000000000030666", + }, + { + "name": "Coachella", + "is_enabled": True, + "description": "Cost Center - Coachella, Id - 5100000000000030667", + }, + { + "name": "Employees", + "is_enabled": True, + "description": "Cost Center - Employees, Id - 200200000000000042900", + }, + { + "name": "Parties", + "is_enabled": True, + "description": "Cost Center - Parties, Id - 200200000000000042901", + }, + { + "name": "Promotional Items", + "is_enabled": True, + "description": "Cost Center - Promotional Items, Id - 200200000000000042903", + }, + { + "name": "Radio", + "is_enabled": True, + "description": "Cost Center - Radio, Id - 5100000000000030668", + }, + { + "name": "Retreats", + "is_enabled": True, + "description": "Cost Center - Retreats, Id - 200200000000000042902", + }, + { + "name": "Test", + "is_enabled": True, + "description": "Cost Center - Test, Id - 5100000000000030855", + } + ] +} diff --git a/tests/test_fyle_integrations_imports/test_modules/test_cost_center.py b/tests/test_fyle_integrations_imports/test_modules/test_cost_center.py new file mode 100644 index 00000000..a52f1c0f --- /dev/null +++ b/tests/test_fyle_integrations_imports/test_modules/test_cost_center.py @@ -0,0 +1,167 @@ +from unittest import mock +from fyle_accounting_mappings.models import ( + DestinationAttribute, + ExpenseAttribute, + Mapping, +) +from apps.quickbooks_online.utils import QBOConnector +from apps.workspaces.models import QBOCredential, FyleCredential, Workspace +from fyle_integrations_platform_connector import PlatformConnector +from fyle_integrations_imports.modules.cost_centers import CostCenter +from tests.test_fyle_integrations_imports.test_modules.fixtures import cost_center_data + + +def test_sync_destination_attributes(mocker, db): + workspace_id = 3 + + mocker.patch( + 'qbosdk.apis.Classes.get', + return_value=cost_center_data['create_new_auto_create_cost_centers_destination_attributes'] + ) + + qbo_credentials = QBOCredential.get_active_qbo_credentials(workspace_id) + qbo_connection = QBOConnector(credentials_object=qbo_credentials, workspace_id=workspace_id) + + destination_attributes_count = DestinationAttribute.objects.filter(workspace_id=3, attribute_type='CLASS').count() + assert destination_attributes_count == 0 + + cost_center = CostCenter(workspace_id, 'CLASS', None, qbo_connection, ['classes']) + cost_center.sync_destination_attributes() + + destination_attributes_count = DestinationAttribute.objects.filter(workspace_id=3, attribute_type='CLASS').count() + assert destination_attributes_count == 2 + + +def test_sync_expense_atrributes(mocker, db): + workspace_id = 3 + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + fyle_credentials.workspace.fyle_org_id = 'or5qYLrvnoF9' + fyle_credentials.workspace.save() + platform = PlatformConnector(fyle_credentials=fyle_credentials) + + qbo_credentials = QBOCredential.get_active_qbo_credentials(workspace_id) + qbo_connection = QBOConnector(credentials_object=qbo_credentials, workspace_id=workspace_id) + + mocker.patch( + 'fyle.platform.apis.v1beta.admin.CostCenters.list_all', + return_value=[] + ) + + cost_center_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='COST_CENTER').count() + assert cost_center_count == 537 + + cost_center = CostCenter(workspace_id, 'CLASS', None, qbo_connection, ['classes']) + cost_center.sync_expense_attributes(platform) + + cost_center_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='COST_CENTER').count() + assert cost_center_count == 537 + + mocker.patch( + 'fyle.platform.apis.v1beta.admin.CostCenters.list_all', + return_value=cost_center_data['create_new_auto_create_cost_centers_expense_attributes_0'] + ) + cost_center.sync_expense_attributes(platform) + + cost_center_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='COST_CENTER').count() + assert cost_center_count == 543 + + +def test_auto_create_destination_attributes(mocker, db): + workspace_id = 3 + qbo_credentials = QBOCredential.get_active_qbo_credentials(workspace_id) + qbo_connection = QBOConnector(credentials_object=qbo_credentials, workspace_id=workspace_id) + cost_center = CostCenter(workspace_id, 'CLASS', None, qbo_connection, ['classes']) + cost_center.sync_after = None + + Workspace.objects.filter(id=workspace_id).update(fyle_org_id='or5qYLrvnoF9') + + # delete all destination attributes, expense attributes and mappings + Mapping.objects.filter(workspace_id=workspace_id, source_type='COST_CENTER').delete() + Mapping.objects.filter(workspace_id=workspace_id, destination_type='CLASS').delete() + DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CLASS').delete() + ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='COST_CENTER').delete() + + # create new case for projects import + with mock.patch('fyle.platform.apis.v1beta.admin.CostCenters.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.CostCenters.post_bulk', + return_value=[] + ) + mocker.patch( + 'qbosdk.apis.Classes.get', + return_value=cost_center_data['create_new_auto_create_cost_centers_destination_attributes'] + ) + mock_call.side_effect = [ + cost_center_data['create_new_auto_create_cost_centers_expense_attributes_0'], + cost_center_data['create_new_auto_create_cost_centers_expense_attributes_1'] + ] + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'COST_CENTER').count() + + assert expense_attributes_count == 0 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='COST_CENTER', destination_type='CLASS').count() + + assert mappings_count == 0 + + cost_center.trigger_import() + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'COST_CENTER').count() + + assert expense_attributes_count == 8 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='COST_CENTER', destination_type='CLASS').count() + + assert mappings_count == 2 + + # create new project sub-sequent run (we will be adding 2 new CLASSES) + with mock.patch('fyle.platform.apis.v1beta.admin.CostCenters.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.CostCenters.post_bulk', + return_value=[] + ) + mocker.patch( + 'qbosdk.apis.Classes.get', + return_value=cost_center_data['create_new_auto_create_cost_centers_destination_attributes_subsequent_run'] + ) + mock_call.side_effect = [ + cost_center_data['create_new_auto_create_cost_centers_expense_attributes_1'], + cost_center_data['create_new_auto_create_cost_centers_expense_attributes_2'] + ] + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'COST_CENTER').count() + + assert expense_attributes_count == 8 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='COST_CENTER', destination_type='CLASS').count() + + assert mappings_count == 2 + + cost_center.trigger_import() + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'COST_CENTER').count() + + assert expense_attributes_count == 10 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='COST_CENTER', destination_type='CLASS').count() + + assert mappings_count == 4 + + +def test_construct_fyle_payload(db): + workspace_id = 5 + qbo_credentials = QBOCredential.get_active_qbo_credentials(workspace_id) + qbo_connection = QBOConnector(credentials_object=qbo_credentials, workspace_id=workspace_id) + cost_center = CostCenter(workspace_id, 'CLASS', None, qbo_connection, ['classes']) + cost_center.sync_after = None + + # create new case + paginated_destination_attributes = DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CLASS') + existing_fyle_attributes_map = {} + + fyle_payload = cost_center.construct_fyle_payload( + paginated_destination_attributes, + existing_fyle_attributes_map, + ) + + assert fyle_payload == cost_center_data['create_fyle_cost_centers_payload_create_new_case'] diff --git a/tests/test_fyle_integrations_imports/test_tasks.py b/tests/test_fyle_integrations_imports/test_tasks.py index ea954343..2cf0dcd4 100644 --- a/tests/test_fyle_integrations_imports/test_tasks.py +++ b/tests/test_fyle_integrations_imports/test_tasks.py @@ -1,8 +1,20 @@ from unittest import mock -from fyle_integrations_imports.tasks import trigger_import_via_schedule +from fyle_integrations_imports.tasks import ( + trigger_import_via_schedule, + disable_category_for_items_mapping +) from fyle_integrations_imports.models import ImportLog from apps.workspaces.models import QBOCredential -from tests.test_fyle_integrations_imports.test_modules.fixtures import projects_data +from apps.tasks.models import Error +from tests.test_fyle_integrations_imports.test_modules.fixtures import ( + projects_data, + categories_data +) +from fyle_accounting_mappings.models import ( + DestinationAttribute, + ExpenseAttribute, + Mapping +) def test_trigger_import_via_schedule(mocker, db): @@ -43,3 +55,100 @@ def test_trigger_import_via_schedule(mocker, db): assert import_logs.status == 'COMPLETE' assert import_logs.attribute_type == 'PROJECT' + + +def test_disable_category_for_items_mapping(mocker, db): + workspace_id = 5 + # delete all the import logs + ImportLog.objects.filter(workspace_id=workspace_id).delete() + credentials = QBOCredential.get_active_qbo_credentials(workspace_id) + + # case where items mappings is not present + with mock.patch('fyle.platform.apis.v1beta.admin.Categories.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.Categories.post_bulk', + return_value=[] + ) + mocker.patch( + 'qbosdk.apis.Items.get', + return_value=categories_data['create_new_auto_create_categories_destination_attributes_items'] + ) + mocker.patch( + 'qbosdk.apis.Accounts.get', + return_value=[] + ) + mock_call.side_effect = [ + categories_data['create_new_auto_create_categories_expense_attributes_1'], + categories_data['create_new_auto_create_categories_expense_attributes_2'] + ] + + disable_category_for_items_mapping( + workspace_id, + 'apps.quickbooks_online.utils.QBOConnector', + credentials + ) + + import_logs = ImportLog.objects.filter(workspace_id=workspace_id).first() + + assert import_logs is None + + # delete all destination attributes, expense attributes and mappings + Error.objects.filter(workspace_id=workspace_id).delete() + Mapping.objects.filter(workspace_id=workspace_id, source_type='CATEGORY').delete() + DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='ACCOUNT').delete() + ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CATEGORY').delete() + + # create the item<>category mapping + destination_attribute = DestinationAttribute.objects.create( + attribute_type='ACCOUNT', + display_name='Item', + value='Machine', + destination_id='Machine', + workspace_id=workspace_id, + active=True + ) + + expense_attribute = ExpenseAttribute.objects.create( + attribute_type='CATEGORY', + value='Machine', + source_id='FyleMachine', + workspace_id=workspace_id, + active=True + ) + + Mapping.objects.create( + source_type='CATEGORY', + destination_type='ACCOUNT', + source_id=expense_attribute.id, + destination_id=destination_attribute.id, + workspace_id=workspace_id, + ) + + with mock.patch('fyle.platform.apis.v1beta.admin.Categories.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.Categories.post_bulk', + return_value=[] + ) + mocker.patch( + 'qbosdk.apis.Items.get', + return_value=categories_data['create_new_auto_create_categories_destination_attributes_items'] + ) + mocker.patch( + 'qbosdk.apis.Accounts.get', + return_value=[] + ) + mock_call.side_effect = [ + categories_data['create_new_auto_create_categories_expense_attributes_1'], + categories_data['create_new_auto_create_categories_expense_attributes_2'] + ] + + disable_category_for_items_mapping( + workspace_id, + 'apps.quickbooks_online.utils.QBOConnector', + credentials + ) + + import_logs = ImportLog.objects.filter(workspace_id=workspace_id).first() + + assert import_logs.status in ['COMPLETE', 'IN_PROGRESS'] + assert import_logs.attribute_type == 'CATEGORY' diff --git a/tests/test_mappings/test_signals.py b/tests/test_mappings/test_signals.py index 0fe1de50..d2082152 100644 --- a/tests/test_mappings/test_signals.py +++ b/tests/test_mappings/test_signals.py @@ -46,24 +46,6 @@ def test_run_post_mapping_settings_triggers(test_connection, mocker): mocker.patch('fyle.platform.apis.v1beta.admin.ExpenseFields.list_all', return_value=fyle_data['get_all_expense_fields']) - mapping_setting = MappingSetting(source_field='PROJECT', destination_field='PROJECT', workspace_id=2, import_to_fyle=True, is_custom=False) - - mapping_setting.save() - - schedule = Schedule.objects.filter(func='apps.mappings.tasks.auto_create_project_mappings', args='{}'.format(2)).first() - - assert schedule.func == 'apps.mappings.tasks.auto_create_project_mappings' - assert schedule.args == '2' - - mapping_setting = MappingSetting(source_field='COST_CENTER', destination_field='CLASS', workspace_id=1, import_to_fyle=True, is_custom=False) - - mapping_setting.save() - - schedule = Schedule.objects.filter(func='apps.mappings.tasks.auto_create_cost_center_mappings', args='{}'.format(1)).first() - - assert schedule.func == 'apps.mappings.tasks.auto_create_cost_center_mappings' - assert schedule.args == '1' - mapping_setting = MappingSetting(source_field='PROJECT', destination_field='DEPARTMENT', workspace_id=1, import_to_fyle=False, is_custom=False) mapping_setting.save() diff --git a/tests/test_mappings/test_tasks.py b/tests/test_mappings/test_tasks.py index 441c3912..ea5f380e 100644 --- a/tests/test_mappings/test_tasks.py +++ b/tests/test_mappings/test_tasks.py @@ -15,7 +15,6 @@ from apps.mappings.queues import ( schedule_auto_map_ccc_employees, schedule_auto_map_employees, - schedule_cost_centers_creation, schedule_fyle_attributes_creation, schedule_tax_groups_creation, ) @@ -24,14 +23,12 @@ async_auto_create_custom_field_mappings, async_auto_map_ccc_account, async_auto_map_employees, - auto_create_cost_center_mappings, auto_create_expense_fields_mappings, auto_create_tax_codes_mappings, auto_create_vendors_as_merchants, auto_import_and_map_fyle_fields, auto_map_ccc_employees, auto_map_employees, - create_fyle_cost_centers_payload, post_merchants, remove_duplicates, resolve_expense_attribute_errors, @@ -184,67 +181,6 @@ def test_schedule_auto_map_ccc_employees(db): assert schedule == None -def test_create_cost_center_payload(db): - existing_cost_center_names = ExpenseAttribute.objects.filter(attribute_type='COST_CENTER', workspace_id=4).values_list('value', flat=True) - - qbo_attributes = DestinationAttribute.objects.filter(attribute_type='CLASS', workspace_id=4).first() - qbo_attributes.value = 'sample333' - qbo_attributes.save() - - qbo_attributes = DestinationAttribute.objects.filter(attribute_type='CLASS', workspace_id=4).order_by('value', 'id') - - qbo_attributes = remove_duplicates(qbo_attributes) - - cost_center_payload = create_fyle_cost_centers_payload(qbo_attributes, existing_cost_center_names) - assert cost_center_payload == [{'description': 'Cost Center - sample333, Id - 5000000000000142238', 'is_enabled': True, 'name': 'sample333'}] - - -def test_auto_create_cost_center_mappings(db, mocker): - workspace_id = 4 - mocker.patch('qbosdk.apis.Classes.get', return_value=[]) - mocker.patch('fyle_integrations_platform_connector.apis.CostCenters.sync', return_value=[]) - mocker.patch('fyle_integrations_platform_connector.apis.CostCenters.post_bulk', return_value=[]) - - qbo_attributes = DestinationAttribute.objects.filter(attribute_type='CLASS', workspace_id=4).first() - qbo_attributes.value = 'sample333' - qbo_attributes.save() - - response = auto_create_cost_center_mappings(workspace_id=workspace_id) - cost_center = DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CLASS').count() - mappings = Mapping.objects.filter(workspace_id=workspace_id, source_type='COST_CENTER').count() - - assert cost_center == 5 - assert mappings == 5 - - with mock.patch('fyle_integrations_platform_connector.apis.CostCenters.sync') as mock_call: - mock_call.side_effect = WrongParamsError(msg='invalid params', response='invalid params') - auto_create_cost_center_mappings(workspace_id) - - mock_call.side_effect = FyleInvalidTokenError(msg='Invalid Token for fyle', response='Invalid Token for fyle') - auto_create_cost_center_mappings(workspace_id) - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - fyle_credentials.delete() - - response = auto_create_cost_center_mappings(workspace_id=workspace_id) - assert response == None - - -def test_schedule_cost_centers_creation(db): - workspace_id = 3 - schedule_cost_centers_creation(import_to_fyle=True, workspace_id=workspace_id) - - schedule = Schedule.objects.filter(func='apps.mappings.tasks.auto_create_cost_center_mappings', args='{}'.format(workspace_id)).first() - - assert schedule.func == 'apps.mappings.tasks.auto_create_cost_center_mappings' - - schedule_cost_centers_creation(import_to_fyle=False, workspace_id=workspace_id) - - schedule = Schedule.objects.filter(func='apps.mappings.tasks.auto_create_cost_center_mappings', args='{}'.format(workspace_id)).first() - - assert schedule == None - - def test_schedule_fyle_attributes_creation(db, mocker): mocker.patch('apps.quickbooks_online.utils.QBOConnector.sync_customers', return_value=None) mocker.patch('fyle_integrations_platform_connector.apis.ExpenseCustomFields.get_by_id', return_value={'options': ['samp'], 'updated_at': '2020-06-11T13:14:55.201598+00:00'})