From 7ee090e675e7013675f50514a029a05d7c837234 Mon Sep 17 00:00:00 2001 From: labhvam5 Date: Sun, 17 Dec 2023 23:40:28 +0530 Subject: [PATCH 1/6] import-cf-0 --- apps/mappings/queues.py | 4 +- apps/mappings/schedules.py | 9 +- apps/mappings/signals.py | 104 +++++++++++++++--- .../apis/import_settings/triggers.py | 7 +- .../test_modules/test_categories.py | 1 - 5 files changed, 101 insertions(+), 24 deletions(-) diff --git a/apps/mappings/queues.py b/apps/mappings/queues.py index 3f044119..ab9990a6 100644 --- a/apps/mappings/queues.py +++ b/apps/mappings/queues.py @@ -127,13 +127,13 @@ 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', 'COST_CENTER']: + if mapping_setting.source_field in ['PROJECT', 'COST_CENTER'] or mapping_setting.is_custom: task_settings['mapping_settings'].append({ 'source_field': mapping_setting.source_field, 'destination_field': mapping_setting.destination_field, 'destination_sync_methods': [SYNC_METHODS[mapping_setting.destination_field]], 'is_auto_sync_enabled': get_auto_sync_permission(workspace_general_settings, mapping_setting), - 'is_custom': False, + 'is_custom': mapping_setting.is_custom }) chain_import_fields_to_fyle(workspace_id, task_settings) diff --git a/apps/mappings/schedules.py b/apps/mappings/schedules.py index 162969cd..9533e99f 100644 --- a/apps/mappings/schedules.py +++ b/apps/mappings/schedules.py @@ -34,8 +34,15 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe source_field__in=['PROJECT', 'COST_CENTER'] ).count() + custom_field_import_fields_count = MappingSetting.objects.filter( + import_to_fyle=True, + workspace_id=workspace_general_settings.workspace_id, + is_custom=True + ).count() + # If the import fields count is 0, delete the schedule - if import_fields_count == 0 and not workspace_general_settings.import_categories and not workspace_general_settings.import_items: + if import_fields_count == 0 and custom_field_import_fields_count == 0\ + and not workspace_general_settings.import_categories and not workspace_general_settings.import_items: Schedule.objects.filter( func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle', args='{}'.format(workspace_general_settings.workspace_id) diff --git a/apps/mappings/signals.py b/apps/mappings/signals.py index 974b5bfe..11b16485 100644 --- a/apps/mappings/signals.py +++ b/apps/mappings/signals.py @@ -1,22 +1,36 @@ """ Mapping Signals """ +import logging from django.db.models.signals import post_delete, post_save, pre_save from django.dispatch import receiver +from rest_framework.exceptions import ValidationError +from datetime import datetime, timedelta, timezone from fyle_accounting_mappings.models import EmployeeMapping, Mapping, MappingSetting - -from apps.mappings.queues import ( - async_auto_create_expense_field_mapping, - schedule_fyle_attributes_creation, -) -from apps.mappings.tasks import upload_attributes_to_fyle +from fyle_integrations_imports.models import ImportLog +from apps.quickbooks_online.utils import QBOConnector +from apps.workspaces.models import FyleCredential, QBOCredential, WorkspaceGeneralSettings +from fyle.platform.exceptions import WrongParamsError +from fyle_integrations_platform_connector import PlatformConnector +from fyle_integrations_imports.modules.expense_custom_fields import ExpenseCustomField # TODO: Fix the naming convention when we remove the old schedule_or_delete_fyle_import_tasks import from helpers.py from apps.mappings.schedules import schedule_or_delete_fyle_import_tasks as new_schedule_or_delete_fyle_import_tasks from apps.tasks.models import Error from apps.workspaces.apis.import_settings.triggers import ImportSettingsTrigger -from apps.workspaces.models import WorkspaceGeneralSettings from apps.workspaces.utils import delete_cards_mapping_settings +logger = logging.getLogger(__name__) + +SYNC_METHODS = { + 'ACCOUNT': 'accounts', + 'ITEM': 'items', + 'VENDOR': 'vendors', + 'DEPARTMENT': 'departments', + 'TAX_CODE': 'tax_codes', + 'CLASS': 'classes', + 'CUSTOMER': 'customers', +} + @receiver(post_save, sender=Mapping) def resolve_post_mapping_errors(sender, instance: Mapping, **kwargs): @@ -50,12 +64,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 in ['PROJECT', 'COST_CENTER']: + if instance.source_field in ['PROJECT', 'COST_CENTER'] or instance.is_custom: new_schedule_or_delete_fyle_import_tasks(workspace_general_settings, instance) - if instance.is_custom: - schedule_fyle_attributes_creation(int(instance.workspace_id)) - if workspace_general_settings: delete_cards_mapping_settings(workspace_general_settings) @@ -77,9 +88,74 @@ def run_pre_mapping_settings_triggers(sender, instance: MappingSetting, **kwargs instance.source_field = instance.source_field.upper().replace(' ', '_') if instance.source_field not in default_attributes: - upload_attributes_to_fyle(workspace_id=int(instance.workspace_id), qbo_attribute_type=instance.destination_field, fyle_attribute_type=instance.source_field, source_placeholder=instance.source_placeholder) - - async_auto_create_expense_field_mapping(instance) + try: + workspace_id = int(instance.workspace_id) + # Checking is import_log exists or not if not create one + import_log, is_created = ImportLog.objects.get_or_create( + workspace_id=workspace_id, + attribute_type=instance.source_field, + defaults={ + 'status': 'IN_PROGRESS' + } + ) + + last_successful_run_at = None + if import_log and not is_created: + last_successful_run_at = import_log.last_successful_run_at if import_log.last_successful_run_at else None + time_difference = datetime.now() - timedelta(minutes=32) + offset_aware_time_difference = time_difference.replace(tzinfo=timezone.utc) + + # if the import_log is present and the last_successful_run_at is less than 30mins then we need to update it + # so that the schedule can run + if last_successful_run_at and offset_aware_time_difference\ + and (offset_aware_time_difference < last_successful_run_at): + import_log.last_successful_run_at = offset_aware_time_difference + last_successful_run_at = offset_aware_time_difference + import_log.save() + + qbo_credentials = QBOCredential.get_active_qbo_credentials(workspace_id) + qbo_connection = QBOConnector(credentials_object=qbo_credentials, workspace_id=workspace_id) + + # Creating the expense_custom_field object with the correct last_successful_run_at value + expense_custom_field = ExpenseCustomField( + workspace_id=workspace_id, + source_field=instance.source_field, + destination_field=instance.destination_field, + sync_after=last_successful_run_at, + sdk_connection=qbo_connection, + destination_sync_methods=[SYNC_METHODS[instance.destination_field]] + ) + + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentials=fyle_credentials) + + # setting the import_log status to IN_PROGRESS + import_log.status = 'IN_PROGRESS' + import_log.save() + + expense_custom_field.construct_payload_and_import_to_fyle(platform, import_log) + expense_custom_field.sync_expense_attributes(platform) + + # NOTE: We are not setting the import_log status to COMPLETE + # since the post_save trigger will run the import again in async manner + + except WrongParamsError as error: + logger.error( + 'Error while creating %s workspace_id - %s in Fyle %s %s', + instance.source_field, instance.workspace_id, error.message, {'error': error.response} + ) + if error.response and 'message' in error.response: + raise ValidationError({ + 'message': error.response['message'], + 'field_name': instance.source_field + }) + + # setting the import_log.last_successful_run_at to -30mins for the post_save_trigger + import_log = ImportLog.objects.filter(workspace_id=workspace_id, attribute_type=instance.source_field).first() + if import_log.last_successful_run_at: + last_successful_run_at = import_log.last_successful_run_at - timedelta(minutes=30) + import_log.last_successful_run_at = last_successful_run_at + import_log.save() @receiver(post_delete, sender=MappingSetting) diff --git a/apps/workspaces/apis/import_settings/triggers.py b/apps/workspaces/apis/import_settings/triggers.py index d9c49d13..c578eb73 100644 --- a/apps/workspaces/apis/import_settings/triggers.py +++ b/apps/workspaces/apis/import_settings/triggers.py @@ -6,8 +6,7 @@ from apps.fyle.models import ExpenseGroupSettings from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks from apps.mappings.queues import ( - schedule_fyle_attributes_creation, - schedule_tax_groups_creation, + schedule_tax_groups_creation ) from apps.workspaces.models import WorkspaceGeneralSettings from apps.mappings.schedules import schedule_or_delete_fyle_import_tasks as new_schedule_or_delete_fyle_import_tasks @@ -99,10 +98,6 @@ def pre_save_mapping_settings(self): """ mapping_settings = self.__mapping_settings - schedule_fyle_attributes_creation(self.__workspace_id) - - # Removal of department grouping will be taken care from post_delete() signal - # Update department mapping to some other Fyle field current_mapping_settings = MappingSetting.objects.filter(workspace_id=self.__workspace_id).all() diff --git a/tests/test_fyle_integrations_imports/test_modules/test_categories.py b/tests/test_fyle_integrations_imports/test_modules/test_categories.py index 59c7f5a5..31f52220 100644 --- a/tests/test_fyle_integrations_imports/test_modules/test_categories.py +++ b/tests/test_fyle_integrations_imports/test_modules/test_categories.py @@ -447,7 +447,6 @@ def test_construct_fyle_payload(db): paginated_destination_attributes = DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='ACCOUNT', display_name='Account') paginated_destination_attribute_values = [attribute.value for attribute in paginated_destination_attributes] - print(paginated_destination_attribute_values) existing_fyle_attributes_map = category.get_existing_fyle_attributes(paginated_destination_attribute_values) fyle_payload = category.construct_fyle_payload( From b9b7712f5fc2a4eb899389b22ff53533a132a04c Mon Sep 17 00:00:00 2001 From: labhvam5 Date: Mon, 18 Dec 2023 12:50:03 +0530 Subject: [PATCH 2/6] submodule commit changes --- fyle_integrations_imports | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fyle_integrations_imports b/fyle_integrations_imports index 63d18ffe..0461ed30 160000 --- a/fyle_integrations_imports +++ b/fyle_integrations_imports @@ -1 +1 @@ -Subproject commit 63d18ffeb56380a18e547e9b32de4c3aa7440494 +Subproject commit 0461ed30250178b3b0df3a09f8e7183334a442d5 From ed93a0d3d320ad9e107d7fd3dabe2c37f6061211 Mon Sep 17 00:00:00 2001 From: labhvam5 Date: Tue, 19 Dec 2023 14:25:50 +0530 Subject: [PATCH 3/6] resolving commnets --- apps/mappings/constants.py | 10 +++++++++ apps/mappings/queues.py | 11 +--------- apps/mappings/schedules.py | 36 +++++++++++++++---------------- apps/mappings/signals.py | 22 ++++--------------- apps/quickbooks_online/actions.py | 12 +---------- fyle_integrations_imports | 2 +- 6 files changed, 35 insertions(+), 58 deletions(-) diff --git a/apps/mappings/constants.py b/apps/mappings/constants.py index 2db2867e..c31a8370 100644 --- a/apps/mappings/constants.py +++ b/apps/mappings/constants.py @@ -43,3 +43,13 @@ 'paid date', 'expense created date', ] + +SYNC_METHODS = { + 'ACCOUNT': 'accounts', + 'ITEM': 'items', + 'VENDOR': 'vendors', + 'DEPARTMENT': 'departments', + 'TAX_CODE': 'tax_codes', + 'CLASS': 'classes', + 'CUSTOMER': 'customers', +} diff --git a/apps/mappings/queues.py b/apps/mappings/queues.py index ab9990a6..fb39f193 100644 --- a/apps/mappings/queues.py +++ b/apps/mappings/queues.py @@ -9,16 +9,7 @@ from apps.mappings.helpers import get_auto_sync_permission from fyle_integrations_imports.queues import chain_import_fields_to_fyle from fyle_integrations_imports.dataclasses import TaskSetting - -SYNC_METHODS = { - 'ACCOUNT': 'accounts', - 'ITEM': 'items', - 'VENDOR': 'vendors', - 'DEPARTMENT': 'departments', - 'TAX_CODE': 'tax_codes', - 'CLASS': 'classes', - 'CUSTOMER': 'customers', -} +from apps.mappings.constants import SYNC_METHODS def async_auto_create_expense_field_mapping(mapping_setting: MappingSetting): diff --git a/apps/mappings/schedules.py b/apps/mappings/schedules.py index 9533e99f..942648df 100644 --- a/apps/mappings/schedules.py +++ b/apps/mappings/schedules.py @@ -26,24 +26,24 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe 'next_run': datetime.now() } ) - return - import_fields_count = MappingSetting.objects.filter( - import_to_fyle=True, - workspace_id=workspace_general_settings.workspace_id, - source_field__in=['PROJECT', 'COST_CENTER'] - ).count() + else: + import_fields_count = MappingSetting.objects.filter( + import_to_fyle=True, + workspace_id=workspace_general_settings.workspace_id, + source_field__in=['PROJECT', 'COST_CENTER'] + ).count() - custom_field_import_fields_count = MappingSetting.objects.filter( - import_to_fyle=True, - workspace_id=workspace_general_settings.workspace_id, - is_custom=True - ).count() + custom_field_import_fields_count = MappingSetting.objects.filter( + import_to_fyle=True, + workspace_id=workspace_general_settings.workspace_id, + is_custom=True + ).count() - # If the import fields count is 0, delete the schedule - if import_fields_count == 0 and custom_field_import_fields_count == 0\ - and not workspace_general_settings.import_categories and not workspace_general_settings.import_items: - Schedule.objects.filter( - func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle', - args='{}'.format(workspace_general_settings.workspace_id) - ).delete() + # If the import fields count is 0, delete the schedule + if import_fields_count == 0 and custom_field_import_fields_count == 0\ + and not workspace_general_settings.import_categories and not workspace_general_settings.import_items: + Schedule.objects.filter( + func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle', + args='{}'.format(workspace_general_settings.workspace_id) + ).delete() diff --git a/apps/mappings/signals.py b/apps/mappings/signals.py index 11b16485..2cb71e1e 100644 --- a/apps/mappings/signals.py +++ b/apps/mappings/signals.py @@ -13,24 +13,13 @@ from fyle.platform.exceptions import WrongParamsError from fyle_integrations_platform_connector import PlatformConnector from fyle_integrations_imports.modules.expense_custom_fields import ExpenseCustomField -# TODO: Fix the naming convention when we remove the old schedule_or_delete_fyle_import_tasks import from helpers.py -from apps.mappings.schedules import schedule_or_delete_fyle_import_tasks as new_schedule_or_delete_fyle_import_tasks from apps.tasks.models import Error from apps.workspaces.apis.import_settings.triggers import ImportSettingsTrigger from apps.workspaces.utils import delete_cards_mapping_settings +from apps.mappings.constants import SYNC_METHODS logger = logging.getLogger(__name__) -SYNC_METHODS = { - 'ACCOUNT': 'accounts', - 'ITEM': 'items', - 'VENDOR': 'vendors', - 'DEPARTMENT': 'departments', - 'TAX_CODE': 'tax_codes', - 'CLASS': 'classes', - 'CUSTOMER': 'customers', -} - @receiver(post_save, sender=Mapping) def resolve_post_mapping_errors(sender, instance: Mapping, **kwargs): @@ -64,9 +53,6 @@ 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 in ['PROJECT', 'COST_CENTER'] or instance.is_custom: - new_schedule_or_delete_fyle_import_tasks(workspace_general_settings, instance) - if workspace_general_settings: delete_cards_mapping_settings(workspace_general_settings) @@ -108,7 +94,7 @@ def run_pre_mapping_settings_triggers(sender, instance: MappingSetting, **kwargs # if the import_log is present and the last_successful_run_at is less than 30mins then we need to update it # so that the schedule can run if last_successful_run_at and offset_aware_time_difference\ - and (offset_aware_time_difference < last_successful_run_at): + and (offset_aware_time_difference < last_successful_run_at): import_log.last_successful_run_at = offset_aware_time_difference last_successful_run_at = offset_aware_time_difference import_log.save() @@ -136,9 +122,9 @@ def run_pre_mapping_settings_triggers(sender, instance: MappingSetting, **kwargs expense_custom_field.construct_payload_and_import_to_fyle(platform, import_log) expense_custom_field.sync_expense_attributes(platform) - # NOTE: We are not setting the import_log status to COMPLETE + # NOTE: We are not setting the import_log status to COMPLETE # since the post_save trigger will run the import again in async manner - + except WrongParamsError as error: logger.error( 'Error while creating %s workspace_id - %s in Fyle %s %s', diff --git a/apps/quickbooks_online/actions.py b/apps/quickbooks_online/actions.py index e08c4441..d057406b 100644 --- a/apps/quickbooks_online/actions.py +++ b/apps/quickbooks_online/actions.py @@ -20,23 +20,13 @@ from apps.workspaces.models import LastExportDetail, QBOCredential, Workspace from .helpers import generate_export_type_and_id +from apps.mappings.constants import SYNC_METHODS logger = logging.getLogger(__name__) 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) diff --git a/fyle_integrations_imports b/fyle_integrations_imports index 0461ed30..f5d87e0f 160000 --- a/fyle_integrations_imports +++ b/fyle_integrations_imports @@ -1 +1 @@ -Subproject commit 0461ed30250178b3b0df3a09f8e7183334a442d5 +Subproject commit f5d87e0f1042eaa40d50c915490e25a628e86b8b From 77146cf7861f330443f335c98f5bb2d59dde39e3 Mon Sep 17 00:00:00 2001 From: labhvam5 Date: Tue, 19 Dec 2023 16:30:57 +0530 Subject: [PATCH 4/6] resolving comments -2 --- apps/mappings/schedules.py | 10 ++++++---- apps/workspaces/apis/import_settings/triggers.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/mappings/schedules.py b/apps/mappings/schedules.py index 942648df..06017674 100644 --- a/apps/mappings/schedules.py +++ b/apps/mappings/schedules.py @@ -1,10 +1,11 @@ from datetime import datetime +from typing import Dict, List from django_q.models import Schedule from apps.workspaces.models import WorkspaceGeneralSettings from fyle_accounting_mappings.models import MappingSetting -def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGeneralSettings, mapping_setting_instance: MappingSetting = None): +def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGeneralSettings, mapping_settings: List[Dict] = []): """ Schedule or delete Fyle import tasks based on the workspace_general_settings and mapping_settings. :param workspace_general_settings: Workspace workspace_general_settings Instance @@ -12,9 +13,10 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe :return: None """ task_to_be_scheduled = None - # Check if there is a task to be scheduled - if mapping_setting_instance and mapping_setting_instance.import_to_fyle: - task_to_be_scheduled = mapping_setting_instance + for mapping_setting in mapping_settings: + if mapping_setting['import_to_fyle']: + task_to_be_scheduled = True + break if task_to_be_scheduled or workspace_general_settings.import_categories or workspace_general_settings.import_items: Schedule.objects.update_or_create( diff --git a/apps/workspaces/apis/import_settings/triggers.py b/apps/workspaces/apis/import_settings/triggers.py index c578eb73..c9e7f862 100644 --- a/apps/workspaces/apis/import_settings/triggers.py +++ b/apps/workspaces/apis/import_settings/triggers.py @@ -115,5 +115,5 @@ def post_save_mapping_settings(self, workspace_general_settings_instance: Worksp self.__update_expense_group_settings_for_departments() - new_schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance, None) + new_schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance, self.__mapping_settings) schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance) From a6e5a54cc4ec59a704cac0f414035ce037c7ddc9 Mon Sep 17 00:00:00 2001 From: labhvam5 Date: Tue, 19 Dec 2023 19:48:25 +0530 Subject: [PATCH 5/6] addign check --- apps/mappings/schedules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/mappings/schedules.py b/apps/mappings/schedules.py index 06017674..4a76fe7e 100644 --- a/apps/mappings/schedules.py +++ b/apps/mappings/schedules.py @@ -14,7 +14,7 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe """ task_to_be_scheduled = None for mapping_setting in mapping_settings: - if mapping_setting['import_to_fyle']: + if mapping_setting['import_to_fyle'] and mapping_setting['source_field'] in ['PROJECT', 'COST_CENTER'] or mapping_setting['is_custom']: task_to_be_scheduled = True break From c3ec1e54705c2961472390c064235cb8ba7f6dd6 Mon Sep 17 00:00:00 2001 From: labhvam5 <88420539+labhvam5@users.noreply.github.com> Date: Tue, 19 Dec 2023 19:54:05 +0530 Subject: [PATCH 6/6] Removing old code (#537) * Removing old code * adding test (#538) * adding test * lint fixes * fix test * sub module changes * fixing test * fixing test * Migration script (#539) --- apps/mappings/queues.py | 20 +- apps/mappings/tasks.py | 103 ------- apps/quickbooks_online/actions.py | 6 +- .../create-update-new-custom-fields-import.py | 51 ++++ .../test_modules/fixtures.py | 264 ++++++++++++++++++ .../test_expense_custom_fields.py | 152 ++++++++++ tests/test_mappings/test_schedules.py | 17 +- tests/test_mappings/test_signals.py | 11 +- tests/test_mappings/test_tasks.py | 32 --- .../test_clone_settings/test_views.py | 8 +- .../test_import_settings/test_views.py | 2 +- 11 files changed, 488 insertions(+), 178 deletions(-) create mode 100644 scripts/python/create-update-new-custom-fields-import.py create mode 100644 tests/test_fyle_integrations_imports/test_modules/test_expense_custom_fields.py diff --git a/apps/mappings/queues.py b/apps/mappings/queues.py index fb39f193..b4f2f4d1 100644 --- a/apps/mappings/queues.py +++ b/apps/mappings/queues.py @@ -1,7 +1,6 @@ -from datetime import datetime, timedelta +from datetime import datetime from django_q.models import Schedule -from django_q.tasks import async_task from fyle_accounting_mappings.models import MappingSetting from apps.mappings.models import GeneralMapping @@ -12,23 +11,6 @@ from apps.mappings.constants import SYNC_METHODS -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_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: - schedule, _ = Schedule.objects.get_or_create( - func='apps.mappings.tasks.async_auto_create_custom_field_mappings', args='{0}'.format(workspace_id), defaults={'schedule_type': Schedule.MINUTES, 'minutes': 24 * 60, 'next_run': datetime.now() + timedelta(hours=24)} - ) - else: - schedule: Schedule = Schedule.objects.filter(func='apps.mappings.tasks.async_auto_create_custom_field_mappings', args='{0}'.format(workspace_id)).first() - - if schedule: - schedule.delete() - - def schedule_bill_payment_creation(sync_fyle_to_qbo_payments, workspace_id): general_mappings: GeneralMapping = GeneralMapping.objects.filter(workspace_id=workspace_id).first() if general_mappings: diff --git a/apps/mappings/tasks.py b/apps/mappings/tasks.py index 0406ed9a..5ea59a34 100644 --- a/apps/mappings/tasks.py +++ b/apps/mappings/tasks.py @@ -1,12 +1,10 @@ import logging from typing import Dict, List -from dateutil import parser from django_q.tasks import Chain from fyle_accounting_mappings.models import DestinationAttribute, EmployeeMapping, ExpenseAttribute, Mapping, MappingSetting from fyle_integrations_platform_connector import PlatformConnector -from apps.mappings.constants import FYLE_EXPENSE_SYSTEM_FIELDS from apps.mappings.exceptions import handle_import_exceptions from apps.mappings.models import GeneralMapping from apps.quickbooks_online.utils import QBOConnector @@ -375,107 +373,6 @@ def create_fyle_tax_group_payload(qbo_attributes: List[DestinationAttribute], ex return fyle_tax_group_payload -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 - :param workspace_id: Workspace ID - :param qbo_attributes: QBO Objects - :param fyle_attribute: Fyle Attribute - :return: Fyle Expense Custom Field Payload - """ - fyle_expense_custom_field_options = [] - - if fyle_attribute.lower() not in FYLE_EXPENSE_SYSTEM_FIELDS: - existing_attribute = ExpenseAttribute.objects.filter(attribute_type=fyle_attribute, workspace_id=workspace_id).values_list('detail', flat=True).first() - - custom_field_id = None - placeholder = None - is_mandatory = False - if existing_attribute is not None: - custom_field_id = existing_attribute['custom_field_id'] - placeholder = existing_attribute['placeholder'] if 'placeholder' in existing_attribute else None - is_mandatory = existing_attribute['is_mandatory'] if 'is_mandatory' in existing_attribute else False - expense_field = platform.expense_custom_fields.get_by_id(custom_field_id) - fyle_expense_custom_field_options = expense_field['options'] - last_imported_at = expense_field['updated_at'] - qbo_attributes = [qbo_attribute for qbo_attribute in qbo_attributes if qbo_attribute.updated_at > parser.parse(last_imported_at)] - - [fyle_expense_custom_field_options.append(qbo_attribute.value) for qbo_attribute in qbo_attributes] - fyle_expense_custom_field_options = list(set(fyle_expense_custom_field_options)) - fyle_attribute = fyle_attribute.replace('_', ' ').title() - - new_placeholder = None - - # Here is the explanation of what's happening in the if-else ladder below - # source_field is the field that's save in mapping settings, this field user may or may not fill in the custom field form - # placeholder is the field that's saved in the detail column of destination attributes - # fyle_attribute is what we're constructing when both of these fields would not be available - - if not (source_placeholder or placeholder): - # If source_placeholder and placeholder are both None, then we're creating adding a self constructed placeholder - new_placeholder = 'Select {0}'.format(fyle_attribute) - elif not source_placeholder and placeholder: - # If source_placeholder is None but placeholder is not, then we're choosing same place holder as 1 in detail section - new_placeholder = placeholder - elif source_placeholder and not placeholder: - # If source_placeholder is not None but placeholder is None, then we're choosing the placeholder as filled by user in form - new_placeholder = source_placeholder - else: - # Else, we're choosing the placeholder as filled by user in form or None - new_placeholder = source_placeholder - - expense_custom_field_payload = {'field_name': fyle_attribute, 'type': 'SELECT', 'is_enabled': True, 'is_mandatory': is_mandatory, 'placeholder': new_placeholder, 'options': fyle_expense_custom_field_options, 'code': None} - - if custom_field_id: - expense_custom_field_payload['id'] = custom_field_id - - return expense_custom_field_payload - - -def upload_attributes_to_fyle(workspace_id: int, qbo_attribute_type: str, fyle_attribute_type: str, source_placeholder: str = None): - """ - Upload attributes to Fyle - """ - fyle_credentials: FyleCredential = FyleCredential.objects.get(workspace_id=workspace_id) - - platform = PlatformConnector(fyle_credentials) - - qbo_attributes: List[DestinationAttribute] = DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type=qbo_attribute_type) - - if qbo_attributes.count(): - qbo_attributes = remove_duplicates(qbo_attributes) - - fyle_custom_field_payload = create_fyle_expense_custom_field_payload(qbo_attributes=qbo_attributes, workspace_id=workspace_id, fyle_attribute=fyle_attribute_type, platform=platform, source_placeholder=source_placeholder) - - if fyle_custom_field_payload: - platform.expense_custom_fields.post(fyle_custom_field_payload) - platform.expense_custom_fields.sync() - - return qbo_attributes - - -@handle_import_exceptions(task_name='Auto Create Expense Fields Mappings') -def auto_create_expense_fields_mappings(workspace_id: int, qbo_attribute_type: str, fyle_attribute_type: str, source_placeholder: str = None): - """ - Create Fyle Attributes Mappings - :return: mappings - """ - fyle_attributes = upload_attributes_to_fyle(workspace_id, qbo_attribute_type, fyle_attribute_type, source_placeholder) - if fyle_attributes: - Mapping.bulk_create_mappings(fyle_attributes, fyle_attribute_type, qbo_attribute_type, workspace_id) - - -@handle_import_exceptions(task_name='Async Auto Create Custom Fields Mappings') -def async_auto_create_custom_field_mappings(workspace_id): - mapping_settings = MappingSetting.objects.filter(is_custom=True, import_to_fyle=True, workspace_id=workspace_id).all() - - if mapping_settings: - for mapping_setting in mapping_settings: - if mapping_setting.import_to_fyle: - sync_qbo_attribute(mapping_setting.destination_field, workspace_id) - auto_create_expense_fields_mappings(workspace_id, mapping_setting.destination_field, mapping_setting.source_field, mapping_setting.source_placeholder) - - def create_fyle_merchants_payload(vendors, existing_merchants_name): payload: List[str] = [] for vendor in vendors: diff --git a/apps/quickbooks_online/actions.py b/apps/quickbooks_online/actions.py index d057406b..d4785458 100644 --- a/apps/quickbooks_online/actions.py +++ b/apps/quickbooks_online/actions.py @@ -72,7 +72,7 @@ def refresh_quickbooks_dimensions(workspace_id: int): chain = Chain() for mapping_setting in mapping_settings: - if mapping_setting.source_field in ['PROJECT', 'COST_CENTER']: + if mapping_setting.source_field in ['PROJECT', 'COST_CENTER'] or mapping_setting.is_custom: chain.append( 'fyle_integrations_imports.tasks.trigger_import_via_schedule', workspace_id, @@ -84,10 +84,8 @@ def refresh_quickbooks_dimensions(workspace_id: int): get_auto_sync_permission(workspace_general_settings, mapping_setting), False, None, - False + mapping_setting.is_custom ) - elif mapping_setting.is_custom: - chain.append('apps.mappings.tasks.async_auto_create_custom_field_mappings', int(workspace_id)) if chain.length() > 0: chain.run() diff --git a/scripts/python/create-update-new-custom-fields-import.py b/scripts/python/create-update-new-custom-fields-import.py new file mode 100644 index 00000000..0c9021c0 --- /dev/null +++ b/scripts/python/create-update-new-custom-fields-import.py @@ -0,0 +1,51 @@ +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.async_auto_create_custom_field_mappings'] +).values('args') + +try: + with transaction.atomic(): + count = 0 + for schedule in existing_import_enabled_schedules: + random_number = random.randint(1, 23) + mapping_setting = MappingSetting.objects.filter(workspace_id=schedule['args'], import_to_fyle=True, is_custom=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.async_auto_create_custom_field_mappings', + args=schedule['args'] + ).delete() + count += 1 + print(""" + + Schedules created + + """) + print(count) + # 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.async_auto_create_custom_field_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.async_auto_create_custom_field_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 ba1dcb2d..cc58c755 100644 --- a/tests/test_fyle_integrations_imports/test_modules/fixtures.py +++ b/tests/test_fyle_integrations_imports/test_modules/fixtures.py @@ -7977,3 +7977,267 @@ } ] } +expense_custom_field_data = { + 'create_new_auto_create_expense_custom_fields_expense_attributes_0':[ + { + "count": 2, + "data": [ + { + "category_ids": [ + 259385 + ], + "code": None, + "column_name": "text_column3", + "created_at": "2023-10-10T11:06:12.906551+00:00", + "default_value": None, + "field_name": "Luke", + "id": 229506, + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "options": [ + "France", + "Denmark" + ], + "org_id": "or5qYLrvnoF9", + "parent_field_id": None, + "placeholder": "Select Luke", + "seq": 1, + "type": "SELECT", + "updated_at": "2023-10-10T13:24:33.787371+00:00" + }, + { + "category_ids": [ + 259385, + ], + "code": None, + "column_name": "text_column4", + "created_at": "2023-10-10T11:07:08.534779+00:00", + "default_value": None, + "field_name": "Cube", + "id": 229507, + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "options": [ + "Butter Cookies 1", + "Butter Cookies 2", + "Enterprise", + "Killua Class", + "Midsize Business", + "Naruto test 1", + "Serizawa test 1", + "Serizawa test 2", + "Service Line 1", + "Service Line 2", + "Service Line 3", + "Small Business" + ], + "org_id": "or5qYLrvnoF9", + "parent_field_id": None, + "placeholder": "Select Cube", + "seq": 1, + "type": "SELECT", + "updated_at": "2023-10-10T13:24:33.787371+00:00" + } + ], + "offset": 0 + } + ], + 'create_new_auto_create_expense_custom_fields_expense_attributes_1':[ + { + "count": 2, + "data": [ + { + "category_ids": [ + 259385 + ], + "code": None, + "column_name": "text_column3", + "created_at": "2023-10-10T11:06:12.906551+00:00", + "default_value": None, + "field_name": "Luke", + "id": 229506, + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "options": [ + "France", + "Denmark", + "Australia", + "Poland" + ], + "org_id": "or5qYLrvnoF9", + "parent_field_id": None, + "placeholder": "Select Luke", + "seq": 1, + "type": "SELECT", + "updated_at": "2023-10-10T13:24:33.787371+00:00" + }, + { + "category_ids": [ + 259385, + ], + "code": None, + "column_name": "text_column4", + "created_at": "2023-10-10T11:07:08.534779+00:00", + "default_value": None, + "field_name": "Cube", + "id": 229507, + "is_custom": True, + "is_enabled": True, + "is_mandatory": False, + "options": [ + "Butter Cookies 1", + "Butter Cookies 2", + "Enterprise", + "Killua Class", + "Midsize Business", + "Naruto test 1", + "Serizawa test 1", + "Serizawa test 2", + "Service Line 1", + "Service Line 2", + "Service Line 3", + "Small Business" + ], + "org_id": "or5qYLrvnoF9", + "parent_field_id": None, + "placeholder": "Select Cube", + "seq": 1, + "type": "SELECT", + "updated_at": "2023-10-10T13:24:33.787371+00:00" + } + ], + "offset": 0 + } + ], + "create_new_auto_create_expense_custom_fields_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_expense_custom_fields_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_expense_custom_fields_get_by_id': { + 'category_ids':[ + 259385, + 259386, + 259387, + 259388, + 259389, + 259390, + 259391, + 259392, + 259393, + 259394, + 259395, + 259396, + 259397, + 259398, + 259399, + ], + 'code':None, + 'column_name':'text_column3', + 'created_at':'2023-10-10T11:06:12.906551+00:00', + 'default_value':None, + 'field_name':'Luke', + 'id':229506, + 'is_custom':True, + 'is_enabled':True, + 'is_mandatory':False, + "options": [ + "France", + "Denmark" + ], + 'org_id':'or5qYLrvnoF9', + 'parent_field_id':None, + 'placeholder':'Select Luke', + 'seq':1, + 'type':'SELECT', + 'updated_at':'2023-10-11T07:42:24.133074+00:00' + }, + "create_fyle_expense_custom_fields_payload_create_new_case": { + 'field_name':'Luke', + 'type':'SELECT', + 'is_enabled':True, + 'is_mandatory':False, + 'placeholder':'Select Luke', + 'options':[ + 'Adidas', + 'cc1', + 'cc2', + 'Coachella', + 'Employees', + 'Parties', + 'Promotional Items', + 'Radio', + 'Retreats', + 'Test' + ], + 'code':None + } +} diff --git a/tests/test_fyle_integrations_imports/test_modules/test_expense_custom_fields.py b/tests/test_fyle_integrations_imports/test_modules/test_expense_custom_fields.py new file mode 100644 index 00000000..e6fdb6e5 --- /dev/null +++ b/tests/test_fyle_integrations_imports/test_modules/test_expense_custom_fields.py @@ -0,0 +1,152 @@ +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.expense_custom_fields import ExpenseCustomField +from tests.test_fyle_integrations_imports.test_modules.fixtures import expense_custom_field_data + + +def test_sync_expense_atrributes(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) + expense_custom_field = ExpenseCustomField(workspace_id, 'LUKE', 'CLASS', None, qbo_connection, ['classes']) + expense_custom_field.sync_after = None + + Workspace.objects.filter(id=workspace_id).update(fyle_org_id='or5qYLrvnoF9') + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentials=fyle_credentials) + + expense_attribute_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='LUKE').count() + assert expense_attribute_count == 0 + + mocker.patch( + 'fyle.platform.apis.v1beta.admin.expense_fields.list_all', + return_value=[] + ) + + expense_custom_field.sync_expense_attributes(platform) + + expense_attribute_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='LUKE').count() + assert expense_attribute_count == 0 + + mocker.patch( + 'fyle.platform.apis.v1beta.admin.expense_fields.list_all', + return_value=expense_custom_field_data['create_new_auto_create_expense_custom_fields_expense_attributes_0'] + ) + + expense_custom_field.sync_expense_attributes(platform) + + expense_attribute_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='LUKE').count() + assert expense_attribute_count == 2 + + +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) + expense_custom_field = ExpenseCustomField(workspace_id, 'LUKE', 'CLASS', None, qbo_connection, ['classes']) + expense_custom_field.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='LUKE').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='LUKE').delete() + + # create new case for projects import + with mock.patch('fyle.platform.apis.v1beta.admin.expense_fields.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.ExpenseCustomFields.post', + return_value=[] + ) + mocker.patch( + 'qbosdk.apis.Classes.get', + return_value=expense_custom_field_data['create_new_auto_create_expense_custom_fields_destination_attributes'] + ) + mock_call.side_effect = [ + expense_custom_field_data['create_new_auto_create_expense_custom_fields_expense_attributes_0'], + ] + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'LUKE').count() + + assert expense_attributes_count == 0 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='LUKE', destination_type='CLASS').count() + + assert mappings_count == 0 + + expense_custom_field.trigger_import() + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'LUKE').count() + + assert expense_attributes_count == 2 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='LUKE', destination_type='CLASS').count() + + assert mappings_count == 2 + + # create new case for projects import + with mock.patch('fyle.platform.apis.v1beta.admin.expense_fields.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.ExpenseCustomFields.post', + return_value=[] + ) + mocker.patch( + 'fyle_integrations_platform_connector.apis.ExpenseCustomFields.get_by_id', + return_value=expense_custom_field_data['create_new_auto_create_expense_custom_fields_get_by_id'] + ) + mocker.patch( + 'qbosdk.apis.Classes.get', + return_value=expense_custom_field_data['create_new_auto_create_expense_custom_fields_destination_attributes_subsequent_run'] + ) + mock_call.side_effect = [ + expense_custom_field_data['create_new_auto_create_expense_custom_fields_expense_attributes_1'], + ] + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'LUKE').count() + + assert expense_attributes_count == 2 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='LUKE', destination_type='CLASS').count() + + assert mappings_count == 2 + + expense_custom_field.trigger_import() + + expense_attributes_count = ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type = 'LUKE').count() + + assert expense_attributes_count == 4 + + mappings_count = Mapping.objects.filter(workspace_id=workspace_id, source_type='LUKE', 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) + expense_custom_field = ExpenseCustomField(workspace_id, 'LUKE', 'CLASS', None, qbo_connection, ['classes']) + expense_custom_field.sync_after = None + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentials=fyle_credentials) + + # create new case + paginated_destination_attributes = DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CLASS') + + fyle_payload = expense_custom_field.construct_fyle_expense_custom_field_payload( + paginated_destination_attributes, + platform, + ) + + print(fyle_payload) + + assert fyle_payload == expense_custom_field_data['create_fyle_expense_custom_fields_payload_create_new_case'] diff --git a/tests/test_mappings/test_schedules.py b/tests/test_mappings/test_schedules.py index 7c75ba41..04fd12ad 100644 --- a/tests/test_mappings/test_schedules.py +++ b/tests/test_mappings/test_schedules.py @@ -16,15 +16,16 @@ def test_schedule_creation(db): configuration.save() MappingSetting.objects.filter(workspace_id=workspace_id).delete() - mapping_setting = MappingSetting.objects.create( - source_field='PROJECT', - destination_field='CUSTOMER', - workspace_id=workspace_id, - import_to_fyle=True, - is_custom=False - ) + mapping_settings = [ + { + 'source_field': 'PROJECT', + 'destination_field': 'CUSTOMER', + 'import_to_fyle': True, + 'is_custom': False + } + ] - schedule_or_delete_fyle_import_tasks(configuration, mapping_setting) + schedule_or_delete_fyle_import_tasks(configuration, mapping_settings) schedule = Schedule.objects.filter( func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle', diff --git a/tests/test_mappings/test_signals.py b/tests/test_mappings/test_signals.py index d2082152..5437bfc0 100644 --- a/tests/test_mappings/test_signals.py +++ b/tests/test_mappings/test_signals.py @@ -1,5 +1,4 @@ import pytest -from django_q.models import Schedule from fyle_accounting_mappings.models import EmployeeMapping, ExpenseAttribute, Mapping, MappingSetting from apps.fyle.models import ExpenseGroupSettings @@ -54,14 +53,6 @@ def test_run_post_mapping_settings_triggers(test_connection, mocker): assert 'project' in expense_group_settings.reimbursable_expense_group_fields assert 'project' in expense_group_settings.corporate_credit_card_expense_group_fields - mapping_setting = MappingSetting(source_field='SAMPLEs', destination_field='SAMPLEs', workspace_id=1, import_to_fyle=True, is_custom=True) - mapping_setting.save() - - schedule = Schedule.objects.filter(func='apps.mappings.tasks.async_auto_create_custom_field_mappings', args='{}'.format(1)).first() - - assert schedule.func == 'apps.mappings.tasks.async_auto_create_custom_field_mappings' - assert schedule.args == '1' - @pytest.mark.django_db() def test_run_post_delete_mapping_settings_triggers(test_connection): @@ -86,7 +77,7 @@ def test_run_pre_mapping_settings_triggers(db, mocker, test_connection): custom_mappings = Mapping.objects.filter(workspace_id=workspace_id, source_type='CUSTOM_INTENTs').count() assert custom_mappings == 0 - mapping_setting = MappingSetting(source_field='CUSTOM_INTENTs', destination_field='CUSTOM_INTENTs', workspace_id=workspace_id, import_to_fyle=True, is_custom=True) + mapping_setting = MappingSetting(source_field='CUSTOM_INTENTs', destination_field='CLASS', workspace_id=workspace_id, import_to_fyle=True, is_custom=True) mapping_setting.save() custom_mappings = Mapping.objects.last() diff --git a/tests/test_mappings/test_tasks.py b/tests/test_mappings/test_tasks.py index ea5f380e..5037e802 100644 --- a/tests/test_mappings/test_tasks.py +++ b/tests/test_mappings/test_tasks.py @@ -15,15 +15,12 @@ from apps.mappings.queues import ( schedule_auto_map_ccc_employees, schedule_auto_map_employees, - schedule_fyle_attributes_creation, schedule_tax_groups_creation, ) from apps.mappings.tasks import ( Chain, - async_auto_create_custom_field_mappings, async_auto_map_ccc_account, async_auto_map_employees, - auto_create_expense_fields_mappings, auto_create_tax_codes_mappings, auto_create_vendors_as_merchants, auto_import_and_map_fyle_fields, @@ -181,35 +178,6 @@ def test_schedule_auto_map_ccc_employees(db): 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'}) - - workspace_id = 4 - schedule_fyle_attributes_creation(workspace_id) - - mocker.patch('fyle_integrations_platform_connector.apis.ExpenseCustomFields.post', return_value=[]) - - schedule = Schedule.objects.filter(func='apps.mappings.tasks.async_auto_create_custom_field_mappings', args='{}'.format(workspace_id)).first() - assert schedule.func == 'apps.mappings.tasks.async_auto_create_custom_field_mappings' - - async_auto_create_custom_field_mappings(workspace_id) - - mapping_settings = MappingSetting.objects.filter(is_custom=True, import_to_fyle=True, workspace_id=workspace_id) - mapping_settings.delete() - - schedule_fyle_attributes_creation(workspace_id) - schedule = Schedule.objects.filter(func='apps.mappings.tasks.async_auto_create_custom_field_mappings', args='{}'.format(workspace_id)).first() - - assert schedule == None - - mocker.patch('apps.mappings.tasks.upload_attributes_to_fyle', return_value=['CUSTOMER']) - - with mock.patch('fyle_accounting_mappings.models.Mapping.bulk_create_mappings') as mock_call: - mock_call.side_effect = WrongParamsError(msg='invalid params', response='invalid params') - auto_create_expense_fields_mappings(workspace_id, 'CUSTOMER', 'CUSTOMER', 'Select CUSTOMER') - - def test_post_merchants(db, mocker): mocker.patch('fyle_integrations_platform_connector.apis.Merchants.get', return_value=data['get_merchants']) mocker.patch('fyle_integrations_platform_connector.apis.Merchants.post', return_value=[]) diff --git a/tests/test_workspaces/test_apis/test_clone_settings/test_views.py b/tests/test_workspaces/test_apis/test_clone_settings/test_views.py index 5469b794..470fbf50 100644 --- a/tests/test_workspaces/test_apis/test_clone_settings/test_views.py +++ b/tests/test_workspaces/test_apis/test_clone_settings/test_views.py @@ -3,6 +3,7 @@ from apps.workspaces.models import Workspace from tests.helper import dict_compare_keys from .fixtures import data +from tests.test_fyle_integrations_imports.test_modules.fixtures import expense_custom_field_data def assert_4xx_cases(api_client, url, payload): @@ -15,12 +16,17 @@ def assert_4xx_cases(api_client, url, payload): assert response.status_code == 400 -def test_clone_settings(api_client, test_connection): +def test_clone_settings(mocker, api_client, test_connection): workspace = Workspace.objects.get(id=1) workspace.name_in_journal_entry = 'MERCHANT' workspace.onboarding_state = 'COMPLETE' workspace.save() + mocker.patch( + 'fyle.platform.apis.v1beta.admin.expense_fields.list_all', + return_value=expense_custom_field_data['create_new_auto_create_expense_custom_fields_expense_attributes_0'] + ) + url = '/api/v2/workspaces/1/clone_settings/' api_client.credentials(HTTP_AUTHORIZATION='Bearer {}'.format(test_connection.access_token)) response = api_client.put( diff --git a/tests/test_workspaces/test_apis/test_import_settings/test_views.py b/tests/test_workspaces/test_apis/test_import_settings/test_views.py index 4573f3f0..912b6ac9 100644 --- a/tests/test_workspaces/test_apis/test_import_settings/test_views.py +++ b/tests/test_workspaces/test_apis/test_import_settings/test_views.py @@ -6,7 +6,7 @@ def test_import_settings(mocker, api_client, test_connection): - 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'}) + mocker.patch('fyle_integrations_platform_connector.apis.ExpenseCustomFields.get_by_id', return_value={'options': ['samp'],"is_mandatory": False, 'updated_at': '2020-06-11T13:14:55.201598+00:00'}) mocker.patch('fyle_integrations_platform_connector.apis.ExpenseCustomFields.post', return_value=None) mocker.patch('fyle_integrations_platform_connector.apis.ExpenseCustomFields.sync', return_value=None) workspace = Workspace.objects.get(id=3)