From 77024034fab4f716eaff91a0092495a36e923842 Mon Sep 17 00:00:00 2001 From: Hrishabh Tiwari <74908943+Hrishabh17@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:36:44 +0530 Subject: [PATCH] Imports refactor master (#322) * Added integrations_imports submodule, made changes in settings (#310) * Refactor imports for Project resource (#311) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Project: added cluster import, fixed minor typo, added new supplier field * Refactor imports for project and cost_center (#314) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Project: added cluster import, fixed minor typo, added new supplier field * Refactor imports costcenter (#312) * Resource Cost_Center: refactored imports * Refactor imports for Project resource (#311) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Project: added cluster import, fixed minor typo, added new supplier field * Refactor imports custom expense (#313) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Cost_Center: refactored imports * Resource Custom Expense Field: refactored imports * Resource Project: added cluster import, fixed minor typo, added new supplier field * Resource Tax Group: refactored imports (#315) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Cost_Center: refactored imports * Resource Custom Expense Field: refactored imports * Resource Project: added cluster import, fixed minor typo, added new supplier field * Resource Tax Group: refactored imports * Resource Tax Group: changed to is_auto_sync to False by default * Resource Category: refactored imports (#316) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Cost_Center: refactored imports * Resource Custom Expense Field: refactored imports * Resource Tax Group: refactored imports * Resource Category: refactored imports * Resource Category: changed is_auto_sync_enabled to True by default * Refactor imports supplier (#318) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Cost_Center: refactored imports * Resource Custom Expense Field: refactored imports * Resource Tax Group: refactored imports * Resource Category: refactored imports * Added RetryException handler and bumped the sdk versions (#306) * Set tasks limit to 1L (#317) * Set tasks limit to 1L * set timeout * Modified the import schedule creation condition --------- Co-authored-by: ruuushhh <66899387+ruuushhh@users.noreply.github.com> * Refactor old flow (#321) * Added integrations_imports submodule, made changes in settings * Resource Project: Added new flow for the Project Import * Typo Fix * Resource Project: removed old import logic for Project * Resource Cost_Center: refactored imports * Resource Custom Expense Field: refactored imports * Resource Tax Group: refactored imports * Resource Category: refactored imports * Added RetryException handler and bumped the sdk versions (#306) * Set tasks limit to 1L (#317) * Set tasks limit to 1L * set timeout * Modified the import schedule creation condition * Removed dead code, add few test cases, added script for adding new schedule for different workspaces * Added post deploy sql script to dump old schedules and delete --------- Co-authored-by: ruuushhh <66899387+ruuushhh@users.noreply.github.com> * Merged changes from master * Update delete workspace func (#319) * Update delete workspace func * Fix comments * upate workspace last synced fix (#320) * Modified the workflow for submodule * Decreased the coverage to 88% --------- Co-authored-by: ruuushhh <66899387+ruuushhh@users.noreply.github.com> --- .github/workflows/codecov.yml | 4 +- .github/workflows/production_deployment.yml | 2 + .github/workflows/pytest.yml | 2 + .github/workflows/staging_deployment.yml | 2 + .gitmodules | 3 + apps/mappings/constants.py | 8 + apps/mappings/exceptions.py | 54 + apps/mappings/helpers.py | 44 +- apps/mappings/queue.py | 134 +- apps/mappings/schedules.py | 50 + apps/mappings/signals.py | 117 +- apps/mappings/tasks.py | 763 +- .../apis/import_settings/serializers.py | 1 - .../apis/import_settings/triggers.py | 42 +- apps/workspaces/utils.py | 11 +- apps/xero/actions.py | 44 +- fyle_integrations_imports | 1 + fyle_xero_api/settings.py | 11 +- fyle_xero_api/tests/settings.py | 5 + scripts/python/add_new_import_schedule.py | 41 + .../024-dump-schedules-to-csv-and-delete.sql | 22 + .../conftest.py | 71 + .../fixtures.py | 7339 +++++++++++++++++ .../test_fyle_integrations_imports/helpers.py | 37 + .../test_modules/test_base.py | 274 + .../test_queue.py | 60 + tests/test_mappings/conftest.py | 37 + tests/test_mappings/test_exceptions.py | 109 + tests/test_mappings/test_helpers.py | 32 +- tests/test_mappings/test_queues.py | 24 + tests/test_mappings/test_signals.py | 16 +- tests/test_mappings/test_tasks.py | 490 +- tests/test_xero/test_views.py | 4 +- 33 files changed, 8435 insertions(+), 1419 deletions(-) create mode 100644 .gitmodules create mode 100644 apps/mappings/schedules.py create mode 160000 fyle_integrations_imports create mode 100644 scripts/python/add_new_import_schedule.py create mode 100644 sql/scripts/024-dump-schedules-to-csv-and-delete.sql create mode 100644 tests/test_fyle_integrations_imports/conftest.py create mode 100644 tests/test_fyle_integrations_imports/fixtures.py create mode 100644 tests/test_fyle_integrations_imports/helpers.py create mode 100644 tests/test_fyle_integrations_imports/test_modules/test_base.py create mode 100644 tests/test_fyle_integrations_imports/test_queue.py create mode 100644 tests/test_mappings/test_exceptions.py create mode 100644 tests/test_mappings/test_queues.py diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2e54cb6d..d1ff3d55 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -13,11 +13,13 @@ jobs: environment: CI Environment steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: Bring up Services and Run Tests run: | docker-compose -f docker-compose-pipeline.yml build docker-compose -f docker-compose-pipeline.yml up -d - docker-compose -f docker-compose-pipeline.yml exec -T api pytest tests/ --cov --cov-report=xml --cov-fail-under=94 + docker-compose -f docker-compose-pipeline.yml exec -T api pytest tests/ --cov --cov-report=xml --cov-fail-under=88 echo "STATUS=$(cat pytest-coverage.txt | grep 'Required test' | awk '{ print $1 }')" >> $GITHUB_ENV echo "FAILED=$(cat test-reports/report.xml | awk -F'=' '{print $5}' | awk -F' ' '{gsub(/"/, "", $1); print $1}')" >> $GITHUB_ENV env: diff --git a/.github/workflows/production_deployment.yml b/.github/workflows/production_deployment.yml index 026e91ba..567ff59c 100644 --- a/.github/workflows/production_deployment.yml +++ b/.github/workflows/production_deployment.yml @@ -10,6 +10,8 @@ jobs: environment: Production steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: push to dockerhub uses: fylein/docker-release-action@master env: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 9ae1ae0d..012d0d87 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -15,6 +15,8 @@ jobs: environment: CI Environment steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: Bring up Services and test for token health run: | docker-compose -f docker-compose-pipeline.yml build diff --git a/.github/workflows/staging_deployment.yml b/.github/workflows/staging_deployment.yml index 842a1eb7..4dfcf222 100644 --- a/.github/workflows/staging_deployment.yml +++ b/.github/workflows/staging_deployment.yml @@ -14,6 +14,8 @@ jobs: environment: Staging steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: push to dockerhub uses: fylein/docker-release-action@master env: diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..88d489e0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "fyle_integrations_imports"] + path = fyle_integrations_imports + url = git@github.com:fylein/fyle_integrations_imports.git diff --git a/apps/mappings/constants.py b/apps/mappings/constants.py index 8879e77b..2fdab38b 100644 --- a/apps/mappings/constants.py +++ b/apps/mappings/constants.py @@ -43,3 +43,11 @@ "paid date", "expense created date", ] + +SYNC_METHODS = { + 'ACCOUNT': 'accounts', + 'ITEM': 'items', + 'TAX_CODE': 'tax_codes', + 'CONTACT': 'contacts', + 'CUSTOMER': 'customers', +} diff --git a/apps/mappings/exceptions.py b/apps/mappings/exceptions.py index 0aea5606..389381f0 100644 --- a/apps/mappings/exceptions.py +++ b/apps/mappings/exceptions.py @@ -14,6 +14,7 @@ from xerosdk.exceptions import WrongParamsError as XeroWrongParamsError from apps.workspaces.models import XeroCredentials +from fyle_integrations_imports.models import ImportLog logger = logging.getLogger(__name__) logger.level = logging.INFO @@ -77,3 +78,56 @@ def new_fn(workspace_id, *args): return new_fn return decorator + + +def handle_import_exceptions_v2(func): + def new_fn(expense_attribute_instance, *args): + import_log: ImportLog = args[0] + workspace_id = import_log.workspace_id + attribute_type = import_log.attribute_type + error = { + 'task': 'Import {0} to Fyle and Auto Create Mappings'.format(attribute_type), + 'workspace_id': workspace_id, + 'message': None, + 'response': None + } + try: + return func(expense_attribute_instance, *args) + except WrongParamsError as exception: + error['message'] = exception.message + error['response'] = exception.response + error['alert'] = True + import_log.status = 'FAILED' + + except InvalidTokenError: + error['message'] = 'Invalid Token for fyle' + error['alert'] = False + import_log.status = 'FAILED' + + except InternalServerError: + error['message'] = 'Internal server error while importing to Fyle' + error['alert'] = True + import_log.status = 'FAILED' + + except (XeroWrongParamsError, XeroInvalidTokenError, XeroCredentials.DoesNotExist) as exception: + error['message'] = 'Invalid Token or Xero credentials does not exist workspace_id - {0}'.format(workspace_id) + error['alert'] = False + error['response'] = exception.__dict__ + import_log.status = 'FAILED' + + except Exception: + response = traceback.format_exc() + error['message'] = 'Something went wrong' + error['response'] = response + error['alert'] = False + import_log.status = 'FATAL' + + if error['alert']: + logger.error(error) + else: + logger.info(error) + + import_log.error_log = error + import_log.save() + + return new_fn diff --git a/apps/mappings/helpers.py b/apps/mappings/helpers.py index e0226461..27353f07 100644 --- a/apps/mappings/helpers.py +++ b/apps/mappings/helpers.py @@ -1,42 +1,14 @@ -from datetime import datetime - -from django_q.models import Schedule from fyle_accounting_mappings.models import MappingSetting - -from apps.fyle.enums import FyleAttributeEnum from apps.workspaces.models import WorkspaceGeneralSettings -def schedule_or_delete_fyle_import_tasks(configuration: WorkspaceGeneralSettings): +def is_auto_sync_allowed(workspace_general_settings: WorkspaceGeneralSettings, mapping_setting: MappingSetting = None): """ - :param configuration: WorkspaceGeneralSettings Instance - :return: None + Get the auto sync permission + :return: bool """ - project_mapping = MappingSetting.objects.filter( - source_field=FyleAttributeEnum.PROJECT, workspace_id=configuration.workspace_id - ).first() - if ( - configuration.import_categories - or (project_mapping and project_mapping.import_to_fyle) - or configuration.import_suppliers_as_merchants - ): - start_datetime = datetime.now() - Schedule.objects.update_or_create( - func="apps.mappings.tasks.auto_import_and_map_fyle_fields", - cluster='import', - args="{}".format(configuration.workspace_id), - defaults={ - "schedule_type": Schedule.MINUTES, - "minutes": 24 * 60, - "next_run": start_datetime, - }, - ) - elif ( - not configuration.import_categories - and not (project_mapping and project_mapping.import_to_fyle) - and not configuration.import_suppliers_as_merchants - ): - Schedule.objects.filter( - func="apps.mappings.tasks.auto_import_and_map_fyle_fields", - args="{}".format(configuration.workspace_id), - ).delete() + is_auto_sync_status_allowed = False + if (mapping_setting and mapping_setting.destination_field == 'CUSTOMER' and mapping_setting.source_field == 'PROJECT') or workspace_general_settings.import_categories: + is_auto_sync_status_allowed = True + + return is_auto_sync_status_allowed diff --git a/apps/mappings/queue.py b/apps/mappings/queue.py index 973f13d0..408f3db6 100644 --- a/apps/mappings/queue.py +++ b/apps/mappings/queue.py @@ -1,7 +1,13 @@ -from datetime import datetime, timedelta +from datetime import datetime from django_q.models import Schedule from fyle_accounting_mappings.models import MappingSetting +from apps.fyle.enums import FyleAttributeEnum +from apps.mappings.constants import SYNC_METHODS +from apps.mappings.helpers import is_auto_sync_allowed +from fyle_integrations_imports.dataclasses import TaskSetting +from fyle_integrations_imports.queues import chain_import_fields_to_fyle +from apps.workspaces.models import WorkspaceGeneralSettings, XeroCredentials def schedule_auto_map_employees(employee_mapping_preference: str, workspace_id: str): @@ -26,71 +32,77 @@ def schedule_auto_map_employees(employee_mapping_preference: str, workspace_id: schedule.delete() -def schedule_cost_centers_creation(import_to_fyle, workspace_id: int): - if import_to_fyle: - schedule, _ = Schedule.objects.update_or_create( - func="apps.mappings.tasks.auto_create_cost_center_mappings", - cluster='import', - 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 construct_tasks_and_chain_import_fields_to_fyle(workspace_id: int): + """ + Construct tasks and chain import fields to fyle + :param workspace_id: Workspace Id + """ + mapping_settings = MappingSetting.objects.filter( + workspace_id=workspace_id, + import_to_fyle=True + ) + workspace_general_settings = WorkspaceGeneralSettings.objects.get( + workspace_id=workspace_id + ) + credentials = XeroCredentials.objects.get( + workspace_id=workspace_id + ) + # import_vendors_as_merchants is not used in xero, placeholder to avoid KeyError + task_settings: TaskSetting = { + 'import_tax': None, + 'import_vendors_as_merchants': None, + 'import_suppliers_as_merchants': None, + 'import_categories': None, + 'import_items': None, + 'mapping_settings': [], + 'credentials': credentials, + 'sdk_connection_string': 'apps.xero.utils.XeroConnector', + 'custom_properties': None + } -def schedule_tax_groups_creation(import_tax_codes, workspace_id): - if import_tax_codes: - schedule, _ = Schedule.objects.update_or_create( - func="apps.mappings.tasks.auto_create_tax_codes_mappings", - cluster='import', - 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_tax_codes_mappings", - args="{}".format(workspace_id), - ).first() + # For now adding only for PROJECT + ALLOWED_SOURCE_FIELDS = [ + FyleAttributeEnum.PROJECT, + FyleAttributeEnum.COST_CENTER, + ] - if schedule: - schedule.delete() + if workspace_general_settings.import_tax_codes: + task_settings['import_tax'] = { + 'destination_field': 'TAX_CODE', + 'destination_sync_methods': [SYNC_METHODS['TAX_CODE']], + 'is_auto_sync_enabled': False, + 'is_3d_mapping': False + } + if workspace_general_settings.import_categories: + task_settings['import_categories'] = { + 'destination_field': 'ACCOUNT', + 'destination_sync_methods': [SYNC_METHODS['ACCOUNT']], + 'is_auto_sync_enabled': True, + 'is_3d_mapping': False, + 'charts_of_accounts': workspace_general_settings.charts_of_accounts + } -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 workspace_general_settings.import_suppliers_as_merchants: + task_settings['custom_properties'] = { + 'func': 'apps.mappings.tasks.auto_create_suppliers_as_merchants', + 'args': { + 'workspace_id': workspace_id + } + } if mapping_settings: - schedule, _ = Schedule.objects.get_or_create( - func="apps.mappings.tasks.async_auto_create_custom_field_mappings", - cluster='import', - 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=workspace_id, - ).first() + for mapping_setting in mapping_settings: + if mapping_setting.source_field in ALLOWED_SOURCE_FIELDS or mapping_setting.is_custom: + task_settings['mapping_settings'].append( + { + 'source_field': mapping_setting.source_field, + 'destination_field': mapping_setting.destination_field, + 'is_custom': mapping_setting.is_custom, + 'destination_sync_methods': [SYNC_METHODS.get(mapping_setting.destination_field.upper(), 'tracking_categories')], + 'is_auto_sync_enabled': is_auto_sync_allowed(workspace_general_settings, mapping_setting) + } + ) - if schedule: - schedule.delete() + chain_import_fields_to_fyle(workspace_id, task_settings) diff --git a/apps/mappings/schedules.py b/apps/mappings/schedules.py new file mode 100644 index 00000000..46b96351 --- /dev/null +++ b/apps/mappings/schedules.py @@ -0,0 +1,50 @@ +from datetime import datetime +from typing import List, Dict +from apps.workspaces.models import WorkspaceGeneralSettings +from django_q.models import Schedule +from fyle_accounting_mappings.models import MappingSetting + + +def new_schedule_or_delete_fyle_import_tasks( + workspace_general_settings_instance: WorkspaceGeneralSettings, + mapping_settings: List[Dict] +): + """ + Schedule or delete fyle import tasks based on the + workspace general settings and mapping settings + :param workspace_general_settings_instance: WorkspaceGeneralSettings instance + :param mapping_settings: List of mapping settings + :return: None + """ + # short-hand notation, it returns True as soon as it encounters import_to_fyle as True + task_to_be_scheduled = any(mapping_setting['import_to_fyle'] for mapping_setting in mapping_settings) + + if ( + task_to_be_scheduled + or workspace_general_settings_instance.import_customers + or workspace_general_settings_instance.import_tax_codes + or workspace_general_settings_instance.import_categories + or workspace_general_settings_instance.import_suppliers_as_merchants + ): + Schedule.objects.update_or_create( + func='apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle', + args='{}'.format(workspace_general_settings_instance.workspace_id), + defaults={ + 'schedule_type':Schedule.MINUTES, + 'minutes': 24 * 60, + 'next_run': datetime.now(), + 'cluster': 'import' + } + ) + else: + import_fields_count = MappingSetting.objects.filter( + workspace_id=workspace_general_settings_instance.workspace_id, + import_to_fyle=True + ).count() + + # if there are no import fields, delete the schedule + if import_fields_count == 0: + Schedule.objects.filter( + func='apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle', + args='{}'.format(workspace_general_settings_instance.workspace_id) + ).delete() diff --git a/apps/mappings/signals.py b/apps/mappings/signals.py index d54b6313..0f961a17 100644 --- a/apps/mappings/signals.py +++ b/apps/mappings/signals.py @@ -2,19 +2,29 @@ Mappings Signal """ - +import logging +from datetime import datetime, timedelta, timezone from django.db.models.signals import post_save, pre_save from django.dispatch import receiver from django_q.tasks import async_task from fyle_accounting_mappings.models import Mapping, MappingSetting from apps.fyle.enums import FyleAttributeEnum -from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks from apps.mappings.models import TenantMapping -from apps.mappings.queue import schedule_cost_centers_creation, schedule_fyle_attributes_creation -from apps.mappings.tasks import upload_attributes_to_fyle from apps.tasks.models import Error from apps.workspaces.models import WorkspaceGeneralSettings +from apps.mappings.schedules import new_schedule_or_delete_fyle_import_tasks +from apps.workspaces.models import XeroCredentials, FyleCredential +from apps.mappings.constants import SYNC_METHODS +from fyle_integrations_imports.models import ImportLog +from apps.xero.utils import XeroConnector +from fyle_integrations_imports.modules.expense_custom_fields import ExpenseCustomField +from fyle_integrations_platform_connector import PlatformConnector +from fyle.platform.exceptions import WrongParamsError +from rest_framework.exceptions import ValidationError + +logger = logging.getLogger(__name__) +logger.level = logging.INFO @receiver(post_save, sender=Mapping) @@ -39,16 +49,19 @@ 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 == FyleAttributeEnum.PROJECT: - schedule_or_delete_fyle_import_tasks(workspace_general_settings) - if instance.source_field == FyleAttributeEnum.COST_CENTER: - schedule_cost_centers_creation( - instance.import_to_fyle, int(instance.workspace_id) - ) + ALLOWED_SOURCE_FIELDS = [ + FyleAttributeEnum.PROJECT, + FyleAttributeEnum.COST_CENTER, + ] - if instance.is_custom: - schedule_fyle_attributes_creation(int(instance.workspace_id)) + if instance.source_field in ALLOWED_SOURCE_FIELDS or instance.is_custom: + new_schedule_or_delete_fyle_import_tasks( + workspace_general_settings_instance=workspace_general_settings, + mapping_settings=MappingSetting.objects.filter( + workspace_id=instance.workspace_id + ).values() + ) @receiver(pre_save, sender=MappingSetting) @@ -70,22 +83,70 @@ 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), - xero_attribute_type=instance.destination_field, - fyle_attribute_type=instance.source_field, - source_placeholder=instance.source_placeholder, - ) - - async_task( - "apps.mappings.tasks.auto_create_expense_fields_mappings", - int(instance.workspace_id), - instance.destination_field, - instance.source_field, - q_options={ - 'cluster': 'import' - } - ) + 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 or None + time_difference = datetime.now() - timedelta(minutes=30) + offset_aware_time_difference = time_difference.replace(tzinfo=timezone.utc) + + 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() + + xero_credentials = XeroCredentials.get_active_xero_credentials(workspace_id=workspace_id) + xero_connection = XeroConnector(credentials_object=xero_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=xero_connection, + destination_sync_methods=[SYNC_METHODS.get(instance.destination_field.upper(), 'tracking_categories')] + ) + + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentials=fyle_credentials) + + import_log.status = 'IN_PROGRESS' + import_log.save() + + expense_custom_field.construct_payload_and_import_to_fyle(platform=platform, import_log=import_log) + expense_custom_field.sync_expense_attributes(platform=platform) + + 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_save, sender=TenantMapping) diff --git a/apps/mappings/tasks.py b/apps/mappings/tasks.py index fd98da31..419931be 100644 --- a/apps/mappings/tasks.py +++ b/apps/mappings/tasks.py @@ -1,75 +1,20 @@ import logging -from typing import Dict, List +from typing import List +from datetime import datetime, timedelta, timezone -from django_q.tasks import Chain -from fyle_accounting_mappings.models import DestinationAttribute, ExpenseAttribute, Mapping, MappingSetting +from fyle_accounting_mappings.models import Mapping from fyle_integrations_platform_connector import PlatformConnector from apps.fyle.enums import FyleAttributeEnum -from apps.mappings.constants import FYLE_EXPENSE_SYSTEM_FIELDS from apps.mappings.exceptions import handle_import_exceptions from apps.tasks.models import Error from apps.workspaces.models import FyleCredential, WorkspaceGeneralSettings, XeroCredentials from apps.xero.utils import XeroConnector +from fyle_integrations_imports.models import ImportLog logger = logging.getLogger(__name__) logger.level = logging.INFO -DEFAULT_FYLE_CATEGORIES = [ - "Activity", - "Train", - "Fuel", - "Snacks", - "Office Supplies", - "Utility", - "Entertainment", - "Others", - "Mileage", - "Food", - "Per Diem", - "Bus", - "Internet", - "Taxi", - "Courier", - "Hotel", - "Professional Services", - "Phone", - "Office Party", - "Flight", - "Software", - "Parking", - "Toll Charge", - "Tax", - "Training", - "Unspecified", -] - - -def disable_expense_attributes(source_field, destination_field, workspace_id): - filter = {"mapping__isnull": False, "mapping__destination_type": destination_field} - - destination_attribute_ids = DestinationAttribute.objects.filter( - attribute_type=destination_field, - active=False, - workspace_id=workspace_id, - **filter - ).values_list("id", flat=True) - - filter = {"mapping__destination_id__in": destination_attribute_ids} - - expense_attributes_to_disable = ExpenseAttribute.objects.filter( - attribute_type=source_field, active=True, **filter - ) - - expense_attributes_ids = [] - if expense_attributes_to_disable: - expense_attributes_ids = [ - expense_attribute.id for expense_attribute in expense_attributes_to_disable - ] - expense_attributes_to_disable.update(active=False) - - return expense_attributes_ids - def resolve_expense_attribute_errors( source_attribute_type: str, @@ -99,156 +44,6 @@ def resolve_expense_attribute_errors( ) -def remove_duplicates(xero_attributes: List[DestinationAttribute]): - unique_attributes = [] - - attribute_values = [] - - for attribute in xero_attributes: - if attribute.value.lower() not in attribute_values: - unique_attributes.append(attribute) - attribute_values.append(attribute.value.lower()) - - return unique_attributes - - -def create_fyle_categories_payload( - categories: List[DestinationAttribute], - workspace_id: int, - category_map: Dict, - updated_categories: List[ExpenseAttribute] = None, -): - """ - Create Fyle Categories Payload from Xero Categories - :param workspace_id: Workspace integer id - :param categories: Xero Categories - :return: Fyle Categories Payload - """ - payload = [] - - if updated_categories: - for category in updated_categories: - destination_id_of_category = ( - category.mapping.first().destination.destination_id - ) - payload.append( - { - "id": category.source_id, - "name": category.value, - "code": destination_id_of_category, - "is_enabled": category.active if category.value != "Unspecified" else True - } - ) - else: - for category in categories: - if category.value.lower() not in category_map: - payload.append( - { - "name": category.value, - "code": category.destination_id, - "is_enabled": category.active, - } - ) - - logger.info("| Importing Categories to Fyle | Content: {{Fyle Payload count: {}}}".format(len(payload))) - return payload - - -def get_all_categories_from_fyle(platform: PlatformConnector): - categories_generator = platform.connection.v1beta.admin.categories.list_all( - query_params={"order": "id.desc"} - ) - categories = [] - - for response in categories_generator: - if response.get("data"): - categories.extend(response["data"]) - - category_name_map = {} - for category in categories: - if category["sub_category"] and category["name"] != category["sub_category"]: - category["name"] = "{0} / {1}".format( - category["name"], category["sub_category"] - ) - category_name_map[category["name"].lower()] = category - - return category_name_map - - -def upload_categories_to_fyle(workspace_id): - """ - Upload categories to Fyle - """ - fyle_credentials: FyleCredential = FyleCredential.objects.get( - workspace_id=workspace_id - ) - xero_credentials: XeroCredentials = XeroCredentials.get_active_xero_credentials( - workspace_id - ) - general_settings = WorkspaceGeneralSettings.objects.filter( - workspace_id=workspace_id - ).first() - platform = PlatformConnector(fyle_credentials) - - category_map = get_all_categories_from_fyle(platform=platform) - - xero_connection = XeroConnector( - credentials_object=xero_credentials, workspace_id=workspace_id - ) - platform.categories.sync() - xero_connection.sync_accounts() - - xero_attributes = DestinationAttribute.objects.filter( - workspace_id=workspace_id, - attribute_type="ACCOUNT", - detail__account_type__in=general_settings.charts_of_accounts, - ).all() - - xero_attributes = remove_duplicates(xero_attributes) - - fyle_payload: List[Dict] = create_fyle_categories_payload( - xero_attributes, workspace_id, category_map - ) - - if fyle_payload: - platform.categories.post_bulk(fyle_payload) - platform.categories.sync() - - category_ids_to_be_changed = disable_expense_attributes( - FyleAttributeEnum.CATEGORY, "ACCOUNT", workspace_id - ) - if category_ids_to_be_changed: - expense_attributes = ExpenseAttribute.objects.filter( - id__in=category_ids_to_be_changed - ) - fyle_payload: List[Dict] = create_fyle_categories_payload( - [], workspace_id, category_map, updated_categories=expense_attributes - ) - platform.categories.post_bulk(fyle_payload) - platform.categories.sync() - - logger.info("| Importing Categories to Fyle | Content: {{Fyle Payload count: {}}}".format(len(fyle_payload))) - return xero_attributes - - -@handle_import_exceptions(task_name="auto create category mappings") -def auto_create_category_mappings(workspace_id): - """ - Create Category Mappings - :return: mappings - """ - - fyle_categories = upload_categories_to_fyle(workspace_id=workspace_id) - - Mapping.bulk_create_mappings(fyle_categories, FyleAttributeEnum.CATEGORY, "ACCOUNT", workspace_id) - - resolve_expense_attribute_errors( - source_attribute_type=FyleAttributeEnum.CATEGORY, workspace_id=workspace_id - ) - - return [] - - @handle_import_exceptions(task_name="async auto map employees") def async_auto_map_employees(workspace_id: int): general_settings = WorkspaceGeneralSettings.objects.get(workspace_id=workspace_id) @@ -269,505 +64,35 @@ def async_auto_map_employees(workspace_id: int): ) -def sync_xero_attributes(xero_attribute_type: str, workspace_id: int): - xero_credentials: XeroCredentials = XeroCredentials.get_active_xero_credentials( - workspace_id - ) - xero_connection = XeroConnector( - credentials_object=xero_credentials, workspace_id=workspace_id - ) - - if xero_attribute_type == "ITEM": - xero_connection.sync_items() - elif xero_attribute_type == "TAX_CODE": - xero_connection.sync_tax_codes() - elif xero_attribute_type == "CUSTOMER": - xero_connection.sync_customers() - else: - xero_connection.sync_tracking_categories() - - -def create_fyle_cost_centers_payload( - xero_attributes: List[DestinationAttribute], existing_fyle_cost_centers: list -): - """ - Create Fyle Cost Centers Payload from xero Objects - :param workspace_id: Workspace integer id - :param xero_attributes: xero Objects - :param fyle_attribute: Fyle Attribute - :return: Fyle Cost Centers Payload - """ - existing_fyle_cost_centers = [ - cost_center.lower() for cost_center in existing_fyle_cost_centers - ] - fyle_cost_centers_payload = [] - - for xero_attribute in xero_attributes: - if xero_attribute.value.lower() not in existing_fyle_cost_centers: - fyle_cost_centers_payload.append( - { - "name": xero_attribute.value, - "is_enabled": True - if xero_attribute.active is None - else xero_attribute.active, - "description": "Cost Center - {0}, Id - {1}".format( - xero_attribute.value, xero_attribute.destination_id - ), - } - ) - - logger.info("| Importing Cost Centers to Fyle | Content: {{Fyle Payload count: {}}}".format(len(fyle_cost_centers_payload))) - return fyle_cost_centers_payload - - -def post_cost_centers_in_batches( - platform: PlatformConnector, workspace_id: int, xero_attribute_type: str -): - existing_cost_center_names = ExpenseAttribute.objects.filter( - attribute_type=FyleAttributeEnum.COST_CENTER, workspace_id=workspace_id - ).values_list("value", flat=True) - - xero_attribute_count = DestinationAttribute.objects.filter( - attribute_type=xero_attribute_type, workspace_id=workspace_id - ).count() - - page_size = 200 - - for offset in range(0, xero_attribute_count, page_size): - limit = offset + page_size - paginated_xero_attributes = DestinationAttribute.objects.filter( - attribute_type=xero_attribute_type, workspace_id=workspace_id - ).order_by("value", "id")[offset:limit] - - paginated_xero_attributes = remove_duplicates(paginated_xero_attributes) - - fyle_payload: List[Dict] = create_fyle_cost_centers_payload( - paginated_xero_attributes, existing_cost_center_names - ) - - if fyle_payload: - platform.cost_centers.post_bulk(fyle_payload) - platform.cost_centers.sync() - - Mapping.bulk_create_mappings( - paginated_xero_attributes, FyleAttributeEnum.COST_CENTER, xero_attribute_type, workspace_id - ) - - -@handle_import_exceptions(task_name="auto create cost center mappings") -def auto_create_cost_center_mappings(workspace_id: int): - """ - 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=FyleAttributeEnum.COST_CENTER, import_to_fyle=True, workspace_id=workspace_id - ) - - platform.cost_centers.sync() - - sync_xero_attributes(mapping_setting.destination_field, workspace_id=workspace_id) - - post_cost_centers_in_batches( - platform, workspace_id, mapping_setting.destination_field - ) - - -def create_fyle_projects_payload( - projects: List[DestinationAttribute], - existing_project_names: list, - updated_projects: List[ExpenseAttribute] = None, -): - """ - Create Fyle Projects Payload from Xero Projects and Customers - :param projects: Xero Projects - :return: Fyle Projects Payload - """ - payload = [] - if updated_projects: - for project in updated_projects: - destination_id_of_project = ( - project.mapping.first().destination.destination_id - ) - payload.append( - { - "id": project.source_id, - "name": project.value, - "code": destination_id_of_project, - "description": "Project - {0}, Id - {1}".format( - project.value, destination_id_of_project - ), - "is_enabled": project.active, - } - ) - else: - existing_project_names = [ - project_name.lower() for project_name in existing_project_names - ] - - for project in projects: - if project.value.lower() not in existing_project_names: - payload.append( - { - "name": project.value, - "code": project.destination_id, - "description": "Project - {0}, Id - {1}".format( - project.value, project.destination_id - ), - "is_enabled": True - if project.active is None - else project.active, - } - ) - - logger.info("| Importing Projects to Fyle | Content: {{Fyle Payload count: {}}}".format(len(payload))) - return payload - - -def disable_renamed_projects(workspace_id, destination_field): - page_size = 200 - expense_attributes_count = ExpenseAttribute.objects.filter( - attribute_type=FyleAttributeEnum.PROJECT, workspace_id=workspace_id, auto_mapped=True - ).count() - expense_attribute_to_be_disabled = [] - for offset in range(0, expense_attributes_count, page_size): - limit = offset + page_size - - fyle_projects = ExpenseAttribute.objects.filter( - workspace_id=workspace_id, attribute_type=FyleAttributeEnum.PROJECT, auto_mapped=True - ).order_by("value", "id")[offset:limit] - - project_names = list(set(field.value for field in fyle_projects)) - - xero_customers = DestinationAttribute.objects.filter( - attribute_type=destination_field, - workspace_id=workspace_id, - value__in=project_names, - ).values_list("value", flat=True) - - for fyle_project in fyle_projects: - if fyle_project.value not in xero_customers: - fyle_project.active = False - fyle_project.save() - expense_attribute_to_be_disabled.append(fyle_project.id) - - return expense_attribute_to_be_disabled - - -def post_projects_in_batches( - platform: PlatformConnector, workspace_id: int, destination_field: str -): - existing_project_names = ExpenseAttribute.objects.filter( - attribute_type=FyleAttributeEnum.PROJECT, workspace_id=workspace_id - ).values_list("value", flat=True) - xero_attributes_count = DestinationAttribute.objects.filter( - attribute_type=destination_field, workspace_id=workspace_id - ).count() - page_size = 200 - - for offset in range(0, xero_attributes_count, page_size): - limit = offset + page_size - paginated_xero_attributes = DestinationAttribute.objects.filter( - attribute_type=destination_field, workspace_id=workspace_id - ).order_by("value", "id")[offset:limit] - - paginated_xero_attributes = remove_duplicates(paginated_xero_attributes) - - fyle_payload: List[Dict] = create_fyle_projects_payload( - paginated_xero_attributes, existing_project_names - ) - - if fyle_payload: - platform.projects.post_bulk(fyle_payload) - platform.projects.sync() - - Mapping.bulk_create_mappings( - paginated_xero_attributes, FyleAttributeEnum.PROJECT, destination_field, workspace_id - ) - - if destination_field == "CUSTOMER": - expense_attribute_to_be_disable = disable_renamed_projects( - workspace_id, destination_field - ) - project_ids_to_be_changed = disable_expense_attributes( - FyleAttributeEnum.PROJECT, "CUSTOMER", workspace_id - ) - project_ids_to_be_changed.extend(expense_attribute_to_be_disable) - if project_ids_to_be_changed: - expense_attributes = ExpenseAttribute.objects.filter( - id__in=project_ids_to_be_changed - ) - fyle_payload: List[Dict] = create_fyle_projects_payload( - projects=[], - existing_project_names=[], - updated_projects=expense_attributes, - ) - platform.projects.post_bulk(fyle_payload) - platform.projects.sync() - - -@handle_import_exceptions(task_name="auto_create_project_mappings") -def auto_create_project_mappings(workspace_id: int): - """ - Create Project Mappings - :return: mappings - """ - - fyle_credentials: FyleCredential = FyleCredential.objects.get( - workspace_id=workspace_id - ) - - platform = PlatformConnector(fyle_credentials) - - platform.projects.sync() - - mapping_setting = MappingSetting.objects.get( - source_field=FyleAttributeEnum.PROJECT, workspace_id=workspace_id - ) - - sync_xero_attributes(mapping_setting.destination_field, workspace_id) - - post_projects_in_batches(platform, workspace_id, mapping_setting.destination_field) - - -def create_fyle_expense_custom_fields_payload( - xero_attributes: List[DestinationAttribute], - workspace_id: int, - fyle_attribute: str, - platform: PlatformConnector, - source_placeholder: str = None, -): - """ - Create Fyle Expense Custom Field Payload from Xero Objects - :param workspace_id: Workspace ID - :param xero_attributes: Xero Objects - :param fyle_attribute: Fyle Attribute - :return: Fyle Expense Custom Field Payload - """ - - fyle_expense_custom_field_options = [] - - [ - fyle_expense_custom_field_options.append(xero_attribute.value) - for xero_attribute in xero_attributes - ] - - 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 - 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 - ) - - 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": False, - "placeholder": new_placeholder, - "options": fyle_expense_custom_field_options, - "code": None, - } - - if custom_field_id: - expense_field = platform.expense_custom_fields.get_by_id(custom_field_id) - expense_custom_field_payload["id"] = custom_field_id - expense_custom_field_payload["is_mandatory"] = expense_field["is_mandatory"] - - logger.info("| Importing Custom Fields to Fyle | Content: {{Fyle Payload count: {}}}".format(len(expense_custom_field_payload))) - return expense_custom_field_payload - - -def upload_attributes_to_fyle( - workspace_id: int, - xero_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) - - xero_attributes: List[DestinationAttribute] = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type=xero_attribute_type - ) - - xero_attributes = remove_duplicates(xero_attributes) - - fyle_custom_field_payload = create_fyle_expense_custom_fields_payload( - xero_attributes=xero_attributes, - workspace_id=workspace_id, - fyle_attribute=fyle_attribute_type, - source_placeholder=source_placeholder, - platform=platform, - ) - - if fyle_custom_field_payload: - platform.expense_custom_fields.post(fyle_custom_field_payload) - platform.expense_custom_fields.sync() - - return xero_attributes - - -@handle_import_exceptions(task_name="auto create expense fields_mappings") -def auto_create_expense_fields_mappings( - workspace_id: int, - xero_attribute_type: str, - fyle_attribute_type: str, - source_placeholder: str = None, -): - """ - Create Fyle Attributes Mappings - :return: mappings - """ +def auto_create_suppliers_as_merchants(workspace_id): + logger.info("| Importing Suppliers as Merchant to Fyle | Content: {{WORKSPACE_ID: {}}}".format(workspace_id)) - fyle_attributes = upload_attributes_to_fyle( + import_log, is_created = ImportLog.objects.get_or_create( workspace_id=workspace_id, - xero_attribute_type=xero_attribute_type, - fyle_attribute_type=fyle_attribute_type, - source_placeholder=source_placeholder, - ) - if fyle_attributes: - Mapping.bulk_create_mappings( - fyle_attributes, fyle_attribute_type, xero_attribute_type, workspace_id - ) - - -@handle_import_exceptions(task_name="async auto create custom field_mappings") -def async_auto_create_custom_field_mappings(workspace_id: str): - mapping_settings = MappingSetting.objects.filter( - is_custom=True, import_to_fyle=True, workspace_id=workspace_id - ).all() - - for mapping_setting in mapping_settings: - if mapping_setting.import_to_fyle: - sync_xero_attributes(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 upload_tax_groups_to_fyle( - platform_connection: PlatformConnector, workspace_id: int -): - existing_tax_codes_name = ExpenseAttribute.objects.filter( - attribute_type=FyleAttributeEnum.TAX_GROUP, workspace_id=workspace_id - ).values_list("value", flat=True) - - xero_attributes = DestinationAttribute.objects.filter( - attribute_type="TAX_CODE", workspace_id=workspace_id - ).order_by("value", "id") - - xero_attributes = remove_duplicates(xero_attributes) - - fyle_payload: List[Dict] = create_fyle_tax_group_payload( - xero_attributes, existing_tax_codes_name + attribute_type="MERCHANT", + defaults={ + 'status': 'IN_PROGRESS' + } ) - if fyle_payload: - platform_connection.tax_groups.post_bulk(fyle_payload) - - platform_connection.tax_groups.sync() - Mapping.bulk_create_mappings(xero_attributes, FyleAttributeEnum.TAX_GROUP, "TAX_CODE", workspace_id) + sync_after = None + if not is_created: + sync_after = import_log.last_successful_run_at if import_log.last_successful_run_at else None -def create_fyle_tax_group_payload( - xero_attributes: List[DestinationAttribute], existing_fyle_tax_groups: list -): - """ - Create Fyle tax Group Payload from Xero Objects - :param existing_fyle_tax_groups: Existing tax groups names - :param xero_attributes: Xero Objects - :return: Fyle tax Group Payload - """ - - existing_fyle_tax_groups = [ - tax_group.lower() for tax_group in existing_fyle_tax_groups - ] - - fyle_tax_group_payload = [] - for xero_attribute in xero_attributes: - if xero_attribute.value.lower() not in existing_fyle_tax_groups: - fyle_tax_group_payload.append( - { - "name": xero_attribute.value, - "is_enabled": True, - "percentage": round((xero_attribute.detail["tax_rate"] / 100), 2), - } - ) - - logger.info("| Importing Tax Groups to Fyle | Content: {{Fyle Payload count: {}}}".format(len(fyle_tax_group_payload))) - return fyle_tax_group_payload - + time_difference = datetime.now() - timedelta(minutes=30) + offset_aware_time_difference = time_difference.replace(tzinfo=timezone.utc) -@handle_import_exceptions(task_name="auto create tax codes_mappings") -def auto_create_tax_codes_mappings(workspace_id: int): - """ - Create Tax Codes Mappings - :return: None - """ - - fyle_credentials: FyleCredential = FyleCredential.objects.get( - workspace_id=workspace_id - ) - - platform = PlatformConnector(fyle_credentials=fyle_credentials) - platform.tax_groups.sync() - - sync_xero_attributes("TAX_CODE", workspace_id) - - upload_tax_groups_to_fyle(platform, workspace_id) + if (import_log.status == 'IN_PROGRESS' and not is_created) \ + or (sync_after and (sync_after > offset_aware_time_difference)): + return + else: + import_log.status = 'IN_PROGRESS' + import_log.processed_batches_count = 0 + import_log.total_batches_count = 0 + import_log.save() -def auto_create_suppliers_as_merchants(workspace_id): fyle_credentials: FyleCredential = FyleCredential.objects.get( workspace_id=workspace_id ) @@ -781,37 +106,9 @@ def auto_create_suppliers_as_merchants(workspace_id): if merchant_names: fyle_connection.merchants.post(merchant_names, skip_existing_merchants=True) - -def auto_import_and_map_fyle_fields(workspace_id): - """ - Auto import and map fyle fields - """ - workspace_general_settings: WorkspaceGeneralSettings = ( - WorkspaceGeneralSettings.objects.get(workspace_id=workspace_id) - ) - project_mapping = MappingSetting.objects.filter( - source_field=FyleAttributeEnum.PROJECT, workspace_id=workspace_general_settings.workspace_id - ).first() - - chain = Chain() - - if workspace_general_settings.import_categories: - chain.append("apps.mappings.tasks.auto_create_category_mappings", workspace_id, q_options={ - 'cluster': 'import' - }) - - if project_mapping and project_mapping.import_to_fyle: - chain.append("apps.mappings.tasks.auto_create_project_mappings", workspace_id, q_options={ - 'cluster': 'import' - }) - - if workspace_general_settings.import_suppliers_as_merchants: - chain.append( - "apps.mappings.tasks.auto_create_suppliers_as_merchants", workspace_id, - q_options={ - 'cluster': 'import' - } - ) - - if chain.length() > 0: - chain.run() + import_log.status = 'COMPLETE' + import_log.last_successful_run_at = datetime.now() + import_log.error_log = [] + import_log.total_batches_count = 0 + import_log.processed_batches_count = 0 + import_log.save() diff --git a/apps/workspaces/apis/import_settings/serializers.py b/apps/workspaces/apis/import_settings/serializers.py index de55c291..9ee8ffcb 100644 --- a/apps/workspaces/apis/import_settings/serializers.py +++ b/apps/workspaces/apis/import_settings/serializers.py @@ -154,7 +154,6 @@ def update(self, instance, validated): trigger.post_save_workspace_general_settings( workspace_general_settings_instance ) - trigger.pre_save_mapping_settings() if workspace_general_settings["import_tax_codes"]: mapping_settings.append( diff --git a/apps/workspaces/apis/import_settings/triggers.py b/apps/workspaces/apis/import_settings/triggers.py index 8b362bfa..30c19636 100644 --- a/apps/workspaces/apis/import_settings/triggers.py +++ b/apps/workspaces/apis/import_settings/triggers.py @@ -3,13 +3,8 @@ from django.db.models import Q from fyle_accounting_mappings.models import MappingSetting -from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks -from apps.mappings.queue import ( - schedule_cost_centers_creation, - schedule_fyle_attributes_creation, - schedule_tax_groups_creation, -) from apps.workspaces.models import WorkspaceGeneralSettings +from apps.mappings.schedules import new_schedule_or_delete_fyle_import_tasks class ImportSettingsTrigger: @@ -33,12 +28,6 @@ def post_save_workspace_general_settings( """ Post save action for workspace general settings """ - # This will take care of auto creating tax mappings - schedule_tax_groups_creation( - import_tax_codes=self.__workspace_general_settings.get("import_tax_codes"), - workspace_id=self.__workspace_id, - ) - if not self.__workspace_general_settings.get("import_customers"): MappingSetting.objects.filter( workspace_id=self.__workspace_id, @@ -46,26 +35,10 @@ def post_save_workspace_general_settings( destination_field="CUSTOMER", ).delete() - schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance) - - def pre_save_mapping_settings(self): - """ - Post save action for mapping settings - """ - mapping_settings = self.__mapping_settings - - cost_center_mapping_available = False - - # Here we are checking if any of the mappings have PROJECT and COST_CENTER mapping - 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) - - # Schdule for auto creating custom field mappings - schedule_fyle_attributes_creation(self.__workspace_id) + new_schedule_or_delete_fyle_import_tasks( + workspace_general_settings_instance=workspace_general_settings_instance, + mapping_settings=self.__mapping_settings, + ) def post_save_mapping_settings( self, workspace_general_settings_instance: WorkspaceGeneralSettings @@ -96,4 +69,7 @@ def post_save_mapping_settings( workspace_id=self.__workspace_id, ).delete() - schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance) + new_schedule_or_delete_fyle_import_tasks( + workspace_general_settings_instance=workspace_general_settings_instance, + mapping_settings=self.__mapping_settings, + ) diff --git a/apps/workspaces/utils.py b/apps/workspaces/utils.py index 7d28fe7b..032fcdae 100644 --- a/apps/workspaces/utils.py +++ b/apps/workspaces/utils.py @@ -12,10 +12,11 @@ from apps.fyle.enums import FyleAttributeEnum from apps.fyle.models import ExpenseGroupSettings -from apps.mappings.queue import schedule_auto_map_employees, schedule_tax_groups_creation +from apps.mappings.queue import schedule_auto_map_employees from apps.workspaces.models import Workspace, WorkspaceGeneralSettings from apps.xero.queue import schedule_payment_creation, schedule_reimbursements_sync, schedule_xero_objects_status_sync from fyle_xero_api.utils import assert_valid +from apps.mappings.schedules import new_schedule_or_delete_fyle_import_tasks def generate_token(authorization_code: str, redirect_uri: str = None) -> str: @@ -221,8 +222,12 @@ def create_or_update_general_settings(general_settings_payload: Dict, workspace_ general_settings_payload["auto_map_employees"], workspace_id ) - schedule_tax_groups_creation( - import_tax_codes=general_settings.import_tax_codes, workspace_id=workspace_id + new_schedule_or_delete_fyle_import_tasks( + workspace_general_settings_instance=workspace_general_settings, + mapping_settings=MappingSetting.objects.filter( + workspace_id=workspace_id, + source_field=FyleAttributeEnum.TAX_GROUP + ).first(), ) return general_settings diff --git a/apps/xero/actions.py b/apps/xero/actions.py index d6a7e13b..0bd2b6d1 100644 --- a/apps/xero/actions.py +++ b/apps/xero/actions.py @@ -6,6 +6,8 @@ from apps.fyle.enums import FyleAttributeEnum from apps.workspaces.models import Workspace, WorkspaceGeneralSettings, XeroCredentials from apps.xero.utils import XeroConnector +from apps.mappings.helpers import is_auto_sync_allowed +from apps.mappings.constants import SYNC_METHODS def get_xero_connector(workspace_id): @@ -37,6 +39,9 @@ def sync_dimensions(workspace_id): def refersh_xero_dimension(workspace_id): workspace_id = workspace_id + xero_credentials = XeroCredentials.get_active_xero_credentials( + workspace_id=workspace_id + ) xero_connector = get_xero_connector(workspace_id=workspace_id) mapping_settings = MappingSetting.objects.filter( @@ -47,29 +52,26 @@ def refersh_xero_dimension(workspace_id): ) chain = Chain() + ALLOWED_SOURCE_FIELDS = [ + FyleAttributeEnum.PROJECT, + FyleAttributeEnum.COST_CENTER, + ] + for mapping_setting in mapping_settings: - if mapping_setting.source_field == FyleAttributeEnum.PROJECT: - # run auto_import_and_map_fyle_fields - chain.append( - "apps.mappings.tasks.auto_import_and_map_fyle_fields", int(workspace_id), - q_options={ - 'cluster': 'import' - } - ) - elif mapping_setting.source_field == FyleAttributeEnum.COST_CENTER: - # run auto_create_cost_center_mappings - chain.append( - "apps.mappings.tasks.auto_create_cost_center_mappings", - int(workspace_id), - q_options={ - 'cluster': 'import' - } - ) - elif mapping_setting.is_custom: - # run async_auto_create_custom_field_mappings + if mapping_setting.source_field in ALLOWED_SOURCE_FIELDS or mapping_setting.is_custom: + # run new_schedule_or_delete_fyle_import_tasks chain.append( - "apps.mappings.tasks.async_auto_create_custom_field_mappings", - int(workspace_id), + 'fyle_integrations_imports.tasks.trigger_import_via_schedule', + workspace_id, + mapping_setting.destination_field, + mapping_setting.source_field, + 'apps.xero.utils.XeroConnector', + xero_credentials, + [SYNC_METHODS.get(mapping_setting.destination_field.upper(), 'tracking_categories')], + is_auto_sync_allowed(workspace_general_settings, mapping_setting), + False, + None, + mapping_setting.is_custom, q_options={ 'cluster': 'import' } diff --git a/fyle_integrations_imports b/fyle_integrations_imports new file mode 160000 index 00000000..aebcd118 --- /dev/null +++ b/fyle_integrations_imports @@ -0,0 +1 @@ +Subproject commit aebcd118f0717c351ad5e07c197f6f5b33289869 diff --git a/fyle_xero_api/settings.py b/fyle_xero_api/settings.py index 4e831ed2..69c4847a 100644 --- a/fyle_xero_api/settings.py +++ b/fyle_xero_api/settings.py @@ -46,6 +46,7 @@ "corsheaders", "fyle_rest_auth", "fyle_accounting_mappings", + "fyle_integrations_imports", # User Created Apps "apps.users", "apps.workspaces", @@ -190,7 +191,15 @@ "handlers": ["debug_logs"], "propagate": True, }, - "gunicorn": {"handlers": ["request_logs"], "level": "INFO", "propagate": False}, + "gunicorn": { + "handlers": ["request_logs"], + "level": "INFO", + "propagate": False + }, + "fyle_integrations_imports": { + "handlers": ["debug_logs"], + "propagate": True + }, }, } diff --git a/fyle_xero_api/tests/settings.py b/fyle_xero_api/tests/settings.py index d35404cd..9fbd4ed4 100644 --- a/fyle_xero_api/tests/settings.py +++ b/fyle_xero_api/tests/settings.py @@ -44,6 +44,7 @@ "corsheaders", "fyle_rest_auth", "fyle_accounting_mappings", + "fyle_integrations_imports", # User Created Apps "apps.users", "apps.workspaces", @@ -185,6 +186,10 @@ "handlers": ["debug_logs"], "propagate": True, }, + "fyle_integrations_imports": { + "handlers": ["debug_logs"], + "propagate": True + }, }, } diff --git a/scripts/python/add_new_import_schedule.py b/scripts/python/add_new_import_schedule.py new file mode 100644 index 00000000..ae92e9e2 --- /dev/null +++ b/scripts/python/add_new_import_schedule.py @@ -0,0 +1,41 @@ +from django.db import transaction +from django_q.models import Schedule + + +existing_import_schedules = ( + Schedule.objects.filter( + func__in=[ + 'apps.mappings.tasks.auto_import_and_map_fyle_fields', + 'apps.mappings.tasks.auto_create_tax_codes_mappings', + 'apps.mappings.tasks.async_auto_create_custom_field_mappings', + 'apps.mappings.tasks.auto_create_cost_center_mappings' + ] + ).values('args').distinct() +) + +try: + with transaction.atomic(): + for schedule in existing_import_schedules: + workspace_id = schedule['args'] + is_new_schedule_created = Schedule.objects.filter( + func='apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle', + args=workspace_id + ).exists() + if not is_new_schedule_created: + old_schedule = Schedule.objects.filter( + args=workspace_id + ).first() + Schedule.objects.create( + func='apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle', + args=workspace_id, + schedule_type=Schedule.MINUTES, + minutes=24 * 60, + next_run=old_schedule.next_run + ) + new_schedule = Schedule.objects.filter( + func='apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle' + ).count() + print(f"Number of schedules created {new_schedule}") + raise Exception('Rollback') +except Exception as e: + print('Error:', e) diff --git a/sql/scripts/024-dump-schedules-to-csv-and-delete.sql b/sql/scripts/024-dump-schedules-to-csv-and-delete.sql new file mode 100644 index 00000000..6a2f639b --- /dev/null +++ b/sql/scripts/024-dump-schedules-to-csv-and-delete.sql @@ -0,0 +1,22 @@ +rollback; +begin; + +create temp table old_schedules as ( + select * from django_q_schedule + where func in ( + 'apps.mappings.tasks.auto_import_and_map_fyle_fields', + 'apps.mappings.tasks.auto_create_tax_codes_mappings', + 'apps.mappings.tasks.async_auto_create_custom_field_mappings', + 'apps.mappings.tasks.auto_create_cost_center_mappings' + ) +); + +\copy (select * from old_schedules) to '/tmp/django_q_schedule.csv' with csv header; + +delete from django_q_schedule +where func in ( + 'apps.mappings.tasks.auto_import_and_map_fyle_fields', + 'apps.mappings.tasks.auto_create_tax_codes_mappings', + 'apps.mappings.tasks.async_auto_create_custom_field_mappings', + 'apps.mappings.tasks.auto_create_cost_center_mappings' +); \ No newline at end of file diff --git a/tests/test_fyle_integrations_imports/conftest.py b/tests/test_fyle_integrations_imports/conftest.py new file mode 100644 index 00000000..080e787b --- /dev/null +++ b/tests/test_fyle_integrations_imports/conftest.py @@ -0,0 +1,71 @@ +import pytest + +from apps.workspaces.models import Workspace, XeroCredentials, FyleCredential +from fyle_accounting_mappings.models import ( + ExpenseAttribute, + DestinationAttribute, + Mapping +) +from apps.mappings.models import TenantMapping + + +@pytest.fixture +def create_temp_workspace(db): + workspace_id = 3 + Workspace.objects.create( + id=workspace_id, + name="Fyle for Hrishabh Testing", + fyle_org_id="Testing123", + xero_short_code="xero123", + last_synced_at=None, + source_synced_at=None, + destination_synced_at=None, + xero_accounts_last_synced_at=None + ) + + +@pytest.fixture +def add_xero_credentials(db, create_temp_workspace): + workspace_id = 3 + XeroCredentials.objects.create( + workspace_id=workspace_id, + refresh_token="refresh_token", + is_expired=False, + ) + + +@pytest.fixture() +def add_fyle_credentials(db): + workspace_id = 3 + + FyleCredential.objects.create( + refresh_token="refresh_token", + workspace_id=workspace_id, + cluster_domain="https://staging.fyle.tech", + ) + + +@pytest.fixture() +def add_tenant_mapping(db, create_temp_workspace): + workspace = Workspace.objects.filter(id = 3).first() + + tenant_mapping = TenantMapping( + tenant_name="sample", tenant_id="wertyui", workspace=workspace + ) + tenant_mapping.save() + + +@pytest.fixture +def create_project_mapping(db, create_temp_workspace): + workspace_id = 3 + expense_attribute = ExpenseAttribute.objects.create(attribute_type='PROJECT', display_name='project', value='Concrete', source_id='src123', workspace_id=workspace_id, active=True) + destination_attribute = DestinationAttribute.objects.create(attribute_type='CUSTOMER', display_name='customer', value='Concrete', destination_id='dest123', workspace_id=workspace_id, active=True) + Mapping.objects.create(source_type='PROJECT', destination_type='CUSTOMER', destination_id=destination_attribute.id, source_id=expense_attribute.id, workspace_id=workspace_id) + + +@pytest.fixture +def create_category_mapping(db, create_temp_workspace): + workspace_id = 3 + expense_attribute = ExpenseAttribute.objects.create(attribute_type='CATEGORY', display_name='category', value='Concrete', source_id='src123', workspace_id=workspace_id, active=True) + destination_attribute = DestinationAttribute.objects.create(attribute_type='ACCOUNT', display_name='account', value='Concrete', destination_id='dest123', workspace_id=workspace_id, active=True) + Mapping.objects.create(source_type='CATEGORY', destination_type='ACCOUNT', destination_id=destination_attribute.id, source_id=expense_attribute.id, workspace_id=workspace_id) diff --git a/tests/test_fyle_integrations_imports/fixtures.py b/tests/test_fyle_integrations_imports/fixtures.py new file mode 100644 index 00000000..3e8f4364 --- /dev/null +++ b/tests/test_fyle_integrations_imports/fixtures.py @@ -0,0 +1,7339 @@ +projects_data = { + "create_new_auto_create_projects_destination_attributes": [ + { + "Id": "428edce3-cade-4416-9967-86f4b9c4cf49", + "Status": "OK", + "ProviderName": "Fyle Staging", + "DateTimeUTC": "/Date(1709802739074)/", + "Contacts": [ + { + "ContactID": "be7fead6-8698-417f-9960-c34bcb1c251f", + "ContactStatus": "ACTIVE", + "Name": "Theresa Brown", + "FirstName": "Theresa", + "LastName": "Brown", + "EmailAddress": "admin1@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591556403+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 10390926.1, + "Overdue": 10390926.1, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "48ff8147-a0aa-4194-9a23-150af9f7dd97", + "ContactStatus": "ACTIVE", + "Name": "Brian Foster", + "FirstName": "Brian", + "LastName": "Foster", + "EmailAddress": "user2@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591559417+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 21882745.45, + "Overdue": 21882745.45, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "3f66eb69-f34b-4208-9354-2d7bf6607b78", + "ContactStatus": "ACTIVE", + "Name": "Natalie Pope", + "FirstName": "Natalie", + "LastName": "Pope", + "EmailAddress": "user3@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591562517+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 6562497.41, + "Overdue": 6562497.41, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "940ca758-600d-4354-839f-2fe8a4c58e2c", + "ContactStatus": "ACTIVE", + "Name": "Samantha Washington", + "FirstName": "Samantha", + "LastName": "Washington", + "EmailAddress": "user4@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591565590+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 10778992.86, + "Overdue": 10778992.86, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "5a671e58-c816-4005-887b-2c34a2ed99bf", + "ContactStatus": "ACTIVE", + "Name": "Chris Curtis", + "FirstName": "Chris", + "LastName": "Curtis", + "EmailAddress": "user5@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591568677+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 20256214.97, + "Overdue": 20256214.97, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "b39b3d6e-1d89-4589-acbb-83a6614c576c", + "ContactStatus": "ACTIVE", + "Name": "Victor Martinez", + "FirstName": "Victor", + "LastName": "Martinez", + "EmailAddress": "user6@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591571737+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 8161709.31, + "Overdue": 8161709.31, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "fcfe24d3-ac29-4bb7-b6e8-bb0ea7d1a12d", + "ContactStatus": "ACTIVE", + "Name": "James Taylor", + "FirstName": "James", + "LastName": "Taylor", + "EmailAddress": "user7@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591574747+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 5098553.78, + "Overdue": 5098553.78, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "8c8196a0-f6cb-4128-a9e5-93271ef05415", + "ContactStatus": "ACTIVE", + "Name": "Jessica Lane", + "FirstName": "Jessica", + "LastName": "Lane", + "EmailAddress": "user8@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591577830+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 12971762.54, + "Overdue": 12971762.54, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "0621c9f8-f481-405f-96e2-dd52a376155e", + "ContactStatus": "ACTIVE", + "Name": "Justin Glass", + "FirstName": "Justin", + "LastName": "Glass", + "EmailAddress": "user9@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591580870+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 6880468.88, + "Overdue": 6880468.88, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "4de1f23e-dc52-4611-8c52-afeae45d360a", + "ContactStatus": "ACTIVE", + "Name": "Matthew Estrada", + "FirstName": "Matthew", + "LastName": "Estrada", + "EmailAddress": "user10@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591584083+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 5253193.37, + "Overdue": 5253193.37, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "c95e0ac9-3556-4c1f-98e4-0c6738ac7b95", + "ContactStatus": "ACTIVE", + "Name": "Joshua Wood", + "FirstName": "Joshua", + "LastName": "Wood", + "EmailAddress": "user1@fyleforshwetestingxero.bro", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668591587180+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 8739786.58, + "Overdue": 8739786.58, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "4b384ece-b2e4-4f83-956a-53a7a3df6def", + "ContactStatus": "ACTIVE", + "Name": "STEAK-N-SHAKE%230664", + "FirstName": "STEAK-N-SHAKE%230664", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668592267947+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "96e4d4ab-7783-417d-9cd3-9b69b54adfa5", + "ContactStatus": "ACTIVE", + "Name": "DOLLAR TREE", + "FirstName": "DOLLAR", + "LastName": "TREE", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668592271027+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "d7bc525c-5d18-4a4e-a32d-f2eda43a44df", + "ContactStatus": "ACTIVE", + "Name": "STEAK-N-SHAKE", + "FirstName": "STEAK-N-SHAKE", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668593068277+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "60363cf8-02d1-43a3-9d59-559db9442f21", + "ContactStatus": "ACTIVE", + "Name": "Credit Card Misc", + "FirstName": "Credit", + "LastName": "Misc", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1668595831040+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 2611074.78, + "Overdue": 2611074.78, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "829d83bb-7125-4290-8bd4-fa4ced417c0a", + "AccountNumber": "123", + "ContactStatus": "ACTIVE", + "Name": "Killua", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1669879449263+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "ba1f8c0d-3e7f-42b4-97b4-7da6e07d5f0b", + "ContactStatus": "ACTIVE", + "Name": "Eren Yager", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1669879571297+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 3696946.12, + "Overdue": 3696946.12, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "63be4599-1972-4a45-b1cd-3574d4181b97", + "ContactStatus": "ACTIVE", + "Name": "Fyle For Arkham Asylum", + "FirstName": "Fyle", + "LastName": "Asylum", + "EmailAddress": "owner@fyleforgotham.in", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1679380914810+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "f19f7543-d990-4719-a180-3b4cbb53b2e6", + "ContactStatus": "ACTIVE", + "Name": "Ashwinn", + "FirstName": "Ashwinn", + "LastName": "", + "EmailAddress": "ashwin.t@fyle.in", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1687259430110+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 13146989.58, + "Overdue": 13146989.58, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "ea8800fc-a568-4f8d-8a19-061f87481304", + "ContactStatus": "ACTIVE", + "Name": "Uber", + "FirstName": "Uber", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689076108580+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 5253193.38, + "Overdue": 5253193.38, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "a4b3c427-a51f-4100-92c4-7a92d1cb0ed7", + "ContactStatus": "ACTIVE", + "Name": "Travel", + "FirstName": "Travel", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689076122050+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "142db0c8-c59d-4281-a876-f4964073c3dd", + "ContactStatus": "ACTIVE", + "Name": "Ola Cabs", + "FirstName": "Ola", + "LastName": "Cabs", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689076134697+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 2757849.95, + "Overdue": 2757849.95, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "1dd84f56-c2c4-43f6-a658-bdf950fc6d29", + "ContactStatus": "ACTIVE", + "Name": "innogy eMobility Solut", + "FirstName": "innogy", + "LastName": "Solut", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689247129133+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "ff02d24a-9637-4d9d-a18f-0603cabd227b", + "ContactStatus": "ACTIVE", + "Name": "Preis fuer Auslandseinsatz", + "FirstName": "Preis", + "LastName": "Auslandseinsatz", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689247148437+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "92bec116-044f-4f8c-a1eb-0820aa85101e", + "ContactStatus": "ACTIVE", + "Name": "Amazon", + "FirstName": "Amazon", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689247165647+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 6024990.68, + "Overdue": 6024990.68, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "853915d4-2a5e-487b-b6bf-d78a3e1e6e9e", + "ContactStatus": "ACTIVE", + "Name": "Dominos", + "FirstName": "Dominos", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689247179047+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 4233300.49, + "Overdue": 4233300.49, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "297b12e8-738c-4269-86a0-0eedec784d79", + "ContactStatus": "ACTIVE", + "Name": "Uber BV", + "FirstName": "Uber", + "LastName": "BV", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1689247182130+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "4190db7f-6cd7-4d91-ad06-09f1d8aed1af", + "AccountNumber": "9087654321", + "ContactStatus": "ACTIVE", + "Name": "Fyle custome", + "FirstName": "Fyle", + "LastName": "customer", + "EmailAddress": "gagaxe6453@rc3s.com", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1689597275473+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 5225460.89, + "Overdue": 5225460.89, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "4351950b-daf9-41cf-9034-af2bf5a70537", + "ContactStatus": "ACTIVE", + "Name": "AMAZON.COM", + "FirstName": "AMAZON.COM", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1691653685323+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "6d133788-344f-459b-80ca-4fe9f8eac114", + "ContactStatus": "ACTIVE", + "Name": "vikas prasad", + "FirstName": "vikas", + "LastName": "prasad", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1693209618227+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "67d87849-fec8-4f50-b5ad-88ef90e27763", + "AccountNumber": "51618167", + "ContactStatus": "ACTIVE", + "Name": "Test Supplier", + "FirstName": "Test", + "LastName": "Supplier", + "EmailAddress": "testSupplier@fyle.in", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1693225554773+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 2806361.09, + "Overdue": 2806361.09, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "1168fd9d-7d91-4a93-b0cb-d2f20cb5508a", + "ContactStatus": "ACTIVE", + "Name": "Test Rushikesh 454", + "FirstName": "Rushikesh", + "EmailAddress": "rushikesh.t@fyle.in", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1693902640343+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 13482.96, + "Overdue": 13482.96, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "60ba1c33-c13b-4215-a4da-92a2e61a569b", + "ContactStatus": "ACTIVE", + "Name": "CIRCLECI.COM", + "FirstName": "CIRCLECI.COM", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695817872140+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "f262de60-a733-4fdb-87d2-5eaf9abc1ef7", + "ContactStatus": "ACTIVE", + "Name": "IHOP %2336", + "FirstName": "IHOP", + "LastName": "%2336", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695817889313+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "5f2986d8-26a4-4912-8aa2-2cfbc1d522c0", + "ContactStatus": "ACTIVE", + "Name": "Apple Inc", + "FirstName": "Apple", + "LastName": "Inc", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695817903690+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "3467cfec-1fc6-473c-b63c-27a1cc289009", + "ContactStatus": "ACTIVE", + "Name": "DIGITALOCEAN.COM", + "FirstName": "DIGITALOCEAN.COM", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695818045033+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "0335399c-64a4-4c55-97b4-4587ddaf0a8e", + "ContactStatus": "ACTIVE", + "Name": "Staples", + "FirstName": "Staples", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695818215663+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "975d3e0a-cab8-44cb-b13a-cdf751c05b62", + "ContactStatus": "ACTIVE", + "Name": "Ashwin from NetSuite", + "FirstName": "Ashwin", + "LastName": "NetSuite", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695818556800+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "1d4bf056-7dd2-4bff-b820-29a5733a24c0", + "ContactStatus": "ACTIVE", + "Name": "Amanda Monroe", + "FirstName": "Amanda", + "LastName": "Monroe", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695818680030+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "f8b30953-9f31-46f6-b91b-066bb547c4f2", + "ContactStatus": "ACTIVE", + "Name": "GOOGLE*CLOUD 00B0B0-F5", + "FirstName": "GOOGLE*CLOUD", + "LastName": "00B0B0-F5", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695818722217+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "113e1580-886d-4c9e-8b50-cda79b2399bb", + "ContactStatus": "ACTIVE", + "Name": "Alexander Valley Vineyards", + "FirstName": "Alexander", + "LastName": "Vineyards", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1695818821197+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "e8d6b624-e4e9-4a8b-9661-147f1d97ea44", + "ContactStatus": "ACTIVE", + "Name": "Ashwin", + "FirstName": "Ashwin", + "LastName": "", + "EmailAddress": "ashwin.t@fyle.in", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1697521797713+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "DefaultCurrency": "USD", + "Balances": { + "AccountsReceivable": {"Outstanding": 0.0, "Overdue": 0.0}, + "AccountsPayable": { + "Outstanding": 8016580.88, + "Overdue": 8016580.88, + }, + }, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "fcc36f44-e48a-4540-b4b1-e8bccaa35ff9", + "ContactStatus": "ACTIVE", + "Name": "Allison Hill", + "FirstName": "Allison", + "LastName": "Hill", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707123758640+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "6129adce-edcc-4813-bff3-70bba529b95d", + "ContactStatus": "ACTIVE", + "Name": "London Postmaster", + "FirstName": "London", + "LastName": "Postmaster", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469046610+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "b55ad9d5-a014-4820-8e23-3ca1757fa867", + "ContactStatus": "ACTIVE", + "Name": "Dominos Pizza", + "FirstName": "Dominos", + "LastName": "Pizza", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469110953+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "561bbb85-0797-43a0-ac99-ba0417e5e4bc", + "ContactStatus": "ACTIVE", + "Name": "AR Day Property Management", + "FirstName": "AR", + "LastName": "Management", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469247113+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "9bff8f96-edda-4c5e-933e-0283d6076ccd", + "ContactStatus": "ACTIVE", + "Name": "Marriott Hotels", + "FirstName": "Marriott", + "LastName": "Hotels", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469445173+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "fc7bc8bd-b8f3-4eb7-a638-0c6e9cd5703c", + "ContactStatus": "ACTIVE", + "Name": "MY LIFE", + "FirstName": "MY", + "LastName": "LIFE", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469455303+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "31a8ac3a-4b27-4771-b362-53a476f4fe86", + "ContactStatus": "ACTIVE", + "Name": "U Clean", + "FirstName": "U", + "LastName": "Clean", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469491943+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "b696f522-d0e2-48c9-bfd3-88c3cff9c775", + "ContactStatus": "ACTIVE", + "Name": "25hoursHotel Das Tour", + "FirstName": "25hoursHotel", + "LastName": "Tour", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469570140+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "2c3bee84-212a-4d5b-a990-89f3afa4b595", + "ContactStatus": "ACTIVE", + "Name": "NOBU HOTEL SHOREDITCH", + "FirstName": "NOBU", + "LastName": "SHOREDITCH", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469624993+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "d0d7e18c-e545-43f9-981f-60072503e1ca", + "ContactStatus": "ACTIVE", + "Name": "SPEEDWAY", + "FirstName": "SPEEDWAY", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469629180+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "e82f5c6d-cb9c-4c03-b06e-66eb11dd8c57", + "ContactStatus": "ACTIVE", + "Name": "Vueling", + "FirstName": "Vueling", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469636790+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "a5cf18aa-929c-47a7-9bc0-befff1155800", + "ContactStatus": "ACTIVE", + "Name": "RITAS", + "FirstName": "RITAS", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469641120+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "4efca9b5-b072-4560-9a1b-0bbf26c45533", + "ContactStatus": "ACTIVE", + "Name": "SENTRY", + "FirstName": "SENTRY", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469645180+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "81ccfcd1-2f98-44ae-b621-93324c438549", + "ContactStatus": "ACTIVE", + "Name": "GOOGLE *CLOUD_011561-8", + "FirstName": "GOOGLE", + "LastName": "*CLOUD_011561-8", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469707733+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "1d794943-0264-4fb7-a736-aed626de42ab", + "ContactStatus": "ACTIVE", + "Name": "Starbucks", + "FirstName": "Starbucks", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469739520+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "126682f7-a6f0-4e64-b66c-37b4464f8234", + "ContactStatus": "ACTIVE", + "Name": "UPAY LTD", + "FirstName": "UPAY", + "LastName": "LTD", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469747883+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "5a091600-0c7a-40af-88b2-abfa06f6cace", + "ContactStatus": "ACTIVE", + "Name": "Labhvam", + "FirstName": "Labhvam", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469752230+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "af699c22-76b9-4fb0-89bf-4833e857685d", + "ContactStatus": "ACTIVE", + "Name": "CARRABBAS", + "FirstName": "CARRABBAS", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469811170+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "2138145e-a63f-4616-94d7-73b9210bef3b", + "ContactStatus": "ACTIVE", + "Name": "TFL TRAVEL CH", + "FirstName": "TFL", + "LastName": "CH", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469872240+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "1d5cc1d9-16eb-4f8c-a68c-f6ce570e230e", + "ContactStatus": "ACTIVE", + "Name": "SumUp *Duncan Alexand", + "FirstName": "SumUp", + "LastName": "Alexand", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469879910+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "86461ac3-9310-47a7-a109-126766e3975c", + "ContactStatus": "ACTIVE", + "Name": "NPM. INC.", + "FirstName": "NPM.", + "LastName": "INC.", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469915460+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "5334bdc0-7b5d-4e28-a9d8-72c78fd9e18e", + "ContactStatus": "ACTIVE", + "Name": "BAMBOOHR HRIS", + "FirstName": "BAMBOOHR", + "LastName": "HRIS", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469930820+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "53d89fe9-87b1-4215-a033-a845c305956e", + "ContactStatus": "ACTIVE", + "Name": "Lyft", + "FirstName": "Lyft", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469970870+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "e4e7e309-ebe1-4d61-b6f7-0611ddc7b609", + "ContactStatus": "ACTIVE", + "Name": "PIZZA HUT", + "FirstName": "PIZZA", + "LastName": "HUT", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707469983743+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "3cd23fba-cbc6-40da-a55f-2b8390514e93", + "ContactStatus": "ACTIVE", + "Name": "CMT UK LTD TAXI FARE", + "FirstName": "CMT", + "LastName": "FARE", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707470028853+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "87d3dbaf-7185-4b4a-9de5-02bbf7151c97", + "ContactStatus": "ACTIVE", + "Name": "THE KENSINGTON HOTEL 1", + "FirstName": "THE", + "LastName": "1", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707470069897+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "2232b63b-10d2-4cae-985b-bbc8e71fed46", + "ContactStatus": "ACTIVE", + "Name": "Kingfisher", + "FirstName": "Kingfisher", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473164680+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "7e641c98-a497-491e-b6a4-be065c57fcdb", + "ContactStatus": "ACTIVE", + "Name": "Redbus", + "FirstName": "Redbus", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473446683+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "44a8f037-f8b0-4bb2-bf08-c23ee223cd93", + "ContactStatus": "ACTIVE", + "Name": "Booking.com", + "FirstName": "Booking.com", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473540960+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "a4085335-6ea3-454d-b5a2-f35ce676b89a", + "ContactStatus": "ACTIVE", + "Name": "UBER TRIP HELP.UBER.CO", + "FirstName": "UBER", + "LastName": "HELP.UBER.CO", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473543197+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "b18c4499-184c-4184-a08e-942e59c035c4", + "ContactStatus": "ACTIVE", + "Name": "WM SUPERCENTER", + "FirstName": "WM", + "LastName": "SUPERCENTER", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473615007+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "8dab2602-5ad9-4099-b689-d2c89972190a", + "ContactStatus": "ACTIVE", + "Name": "Srav", + "FirstName": "Srav", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473730757+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "5970e23f-fc31-43c3-aa81-d100301fe89c", + "ContactStatus": "ACTIVE", + "Name": "CARAVAN", + "FirstName": "CARAVAN", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473787803+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "81284044-fe0b-4db9-b66f-b6b67a67975e", + "ContactStatus": "ACTIVE", + "Name": "TARGET", + "FirstName": "TARGET", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473792153+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "926472d9-c859-4f27-bf5d-2ca5a5d005c2", + "ContactStatus": "ACTIVE", + "Name": "PUBLIX", + "FirstName": "PUBLIX", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1707473848527+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "2be3af9d-f2d0-49f3-99c7-a8bdddf3d539", + "ContactStatus": "ACTIVE", + "Name": "Hrishabh Test", + "FirstName": "Hrishabh", + "LastName": "Test", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1709188866747+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "f50cda90-8246-46de-a92c-8bcc22de1819", + "AccountNumber": "1424", + "ContactStatus": "ACTIVE", + "Name": "Test Supplier Test New", + "FirstName": "Testing", + "LastName": "Supplier new", + "Addresses": [{"AddressType": "POBOX"}, {"AddressType": "STREET"}], + "Phones": [ + {"PhoneType": "DDI"}, + {"PhoneType": "DEFAULT"}, + {"PhoneType": "FAX"}, + {"PhoneType": "MOBILE"}, + ], + "UpdatedDateUTC": "/Date(1709287081097+0000)/", + "ContactGroups": [], + "IsSupplier": True, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "d2b6c8ac-7731-48f7-a8a1-b1e7c3e01c3b", + "ContactStatus": "ACTIVE", + "Name": "Arsalan", + "FirstName": "Arsalan", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530229070+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "c67bf7f0-49a1-4d5e-9793-f02c923b18ee", + "ContactStatus": "ACTIVE", + "Name": "Domino'S Pizza", + "FirstName": "Domino'S", + "LastName": "Pizza", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530249510+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "e57681b5-9a4b-47df-b47e-06f841c43af7", + "ContactStatus": "ACTIVE", + "Name": "Shiv Sagar", + "FirstName": "Shiv", + "LastName": "Sagar", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530257927+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "24333dad-faf4-420d-9a95-121a432b3c1d", + "ContactStatus": "ACTIVE", + "Name": "SONIC DRIVE IN", + "FirstName": "SONIC", + "LastName": "IN", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530358700+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "014ee8f5-5f82-46f3-b3f0-be7b20c4d830", + "ContactStatus": "ACTIVE", + "Name": "LOLLICUPSTORE", + "FirstName": "LOLLICUPSTORE", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530366557+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "348b5b60-32d9-47dc-add6-2eaf6d8fade4", + "ContactStatus": "ACTIVE", + "Name": "CHICK-FIL-A", + "FirstName": "CHICK-FIL-A", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530448233+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "0deeb9d4-8d75-448a-855b-b30667442afc", + "ContactStatus": "ACTIVE", + "Name": "PANERA %23601223", + "FirstName": "PANERA", + "LastName": "%23601223", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530453187+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "819d92c3-f35d-43b4-97fc-c13c361ed914", + "ContactStatus": "ACTIVE", + "Name": "vendor import", + "FirstName": "vendor", + "LastName": "import", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530466933+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "7b97d03c-fa35-4ab7-8c5f-6bc92de2714a", + "ContactStatus": "ACTIVE", + "Name": "STARBUCKS STORE", + "FirstName": "STARBUCKS", + "LastName": "STORE", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530488520+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "1a77f0fa-2730-421b-bdff-bc9cc86b3134", + "ContactStatus": "ACTIVE", + "Name": "goIbibo", + "FirstName": "goIbibo", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530500880+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "ba7361b0-63d4-48a3-ac7d-5d0d2ac2f3ef", + "ContactStatus": "ACTIVE", + "Name": "DUNKIN PLAZA", + "FirstName": "DUNKIN", + "LastName": "PLAZA", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530505653+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "2bfd3761-7618-4c48-8e7c-bf8f09a2602d", + "ContactStatus": "ACTIVE", + "Name": "THE FLAME BROILER", + "FirstName": "THE", + "LastName": "BROILER", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530524377+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "01ddef1e-e49f-4831-899d-d8d651004530", + "ContactStatus": "ACTIVE", + "Name": "Bob's Burger Joint", + "FirstName": "Bob's", + "LastName": "Joint", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530574273+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "6e159cc1-e2ed-4fd0-bfd9-b6659a594836", + "ContactStatus": "ACTIVE", + "Name": "Books by Bessie", + "FirstName": "Books", + "LastName": "Bessie", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530578580+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "976c30a1-dfbc-499a-b2c9-d7a91f33efbe", + "ContactStatus": "ACTIVE", + "Name": "Brosnahan Insurance Agency", + "FirstName": "Brosnahan", + "LastName": "Agency", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530583653+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "b48108d7-fac4-4276-9dba-42dbd7e86356", + "ContactStatus": "ACTIVE", + "Name": "light", + "FirstName": "light", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530588907+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "734da207-edbf-4b62-9c15-3c27fe92ff10", + "ContactStatus": "ACTIVE", + "Name": "sravan", + "FirstName": "sravan", + "LastName": "", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530598070+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "beeffd6b-992f-435e-843f-6757e253789d", + "ContactStatus": "ACTIVE", + "Name": "Cal Telephone", + "FirstName": "Cal", + "LastName": "Telephone", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530602360+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "66f27092-19e1-4a6e-bf17-e9a4b44ecdfb", + "ContactStatus": "ACTIVE", + "Name": "Cigna Health Care", + "FirstName": "Cigna", + "LastName": "Care", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530607877+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "009b06eb-5f4e-4e66-aae2-6e533ce9cf26", + "ContactStatus": "ACTIVE", + "Name": "Computers by Jenni", + "FirstName": "Computers", + "LastName": "Jenni", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530612827+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "082adc51-1632-4281-b269-69d25e4364cb", + "ContactStatus": "ACTIVE", + "Name": "Abhishek 2", + "FirstName": "Abhishek", + "LastName": "2", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530631453+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "8646b8f7-6bef-419f-befc-10e6a2cdfa86", + "ContactStatus": "ACTIVE", + "Name": "Edward Blankenship", + "FirstName": "Edward", + "LastName": "Blankenship", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530658447+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "09c80b69-81c8-41fb-be52-6125fbda0516", + "ContactStatus": "ACTIVE", + "Name": "Abhishek ji", + "FirstName": "Abhishek", + "LastName": "ji", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530692113+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "08abdd02-4f50-4294-8406-4379ffebdf76", + "ContactStatus": "ACTIVE", + "Name": "GOOGLE*GOOGLE STORAGE IRELAND IRL", + "FirstName": "GOOGLE*GOOGLE", + "LastName": "IRL", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530697280+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "4eaaa041-e9b3-4c29-994a-fd61e7f477aa", + "ContactStatus": "ACTIVE", + "Name": "Nilesh Pant", + "FirstName": "Nilesh", + "LastName": "Pant", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530717230+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "d472e5b8-eb1a-4b78-a95a-73921bc09035", + "ContactStatus": "ACTIVE", + "Name": "Purchase Marshall Tag 2562754042", + "FirstName": "Purchase", + "LastName": "2562754042", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530730703+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "3c9032e2-d869-4017-a17f-e9effc99d048", + "ContactStatus": "ACTIVE", + "Name": "Purchase Taxhandlingfee2562754042", + "FirstName": "Purchase", + "LastName": "Taxhandlingfee2562754042", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530737377+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "0a43aa54-0e93-43b1-84fe-2d880b71c917", + "ContactStatus": "ACTIVE", + "Name": "Chin's Gas and Oil", + "FirstName": "Chin's", + "LastName": "Oil", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530743060+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + { + "ContactID": "2dc5d0f1-7016-4815-8510-1c696dfb6fcf", + "ContactStatus": "ACTIVE", + "Name": "new vendor import", + "FirstName": "new", + "LastName": "import", + "EmailAddress": "", + "BankAccountDetails": "", + "Addresses": [ + { + "AddressType": "STREET", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + { + "AddressType": "POBOX", + "City": "", + "Region": "", + "PostalCode": "", + "Country": "", + }, + ], + "Phones": [ + { + "PhoneType": "DDI", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "DEFAULT", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "FAX", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + { + "PhoneType": "MOBILE", + "PhoneNumber": "", + "PhoneAreaCode": "", + "PhoneCountryCode": "", + }, + ], + "UpdatedDateUTC": "/Date(1709530859400+0000)/", + "ContactGroups": [], + "IsSupplier": False, + "IsCustomer": False, + "ContactPersons": [], + "HasAttachments": False, + "HasValidationErrors": False, + }, + ], + } + ], + "create_new_auto_create_projects_expense_attributes_0": [ + { + "count": 1, + "data": [ + { + "approver_user_ids": [], + "approver_users": [], + "category_ids": None, + "code": "236", + "created_at": "2023-11-19T08:03:34.145903+00:00", + "description": "Xero Project - Dylan, Id - 236", + "display_name": "Dylan", + "id": 324523, + "is_enabled": False, + "name": "Dylan", + "org_id": "or5qYLrvnoF9", + "restricted_spender_user_ids": None, + "sub_project": None, + "updated_at": "2023-11-19T08:11:33.531211+00:00", + } + ], + "offset": 0, + } + ], + "create_new_auto_create_projects_expense_attributes_1": [ + { + "count": 1, + "data": [ + { + "approver_user_ids": [], + "approver_users": [], + "category_ids": None, + "code": "236", + "created_at": "2023-11-19T08:03:34.144823+00:00", + "description": "Xero Project - David, Id - 236", + "display_name": "David", + "id": 3242523, + "is_enabled": True, + "name": "David", + "org_id": "or5qYLrvnoF9", + "restricted_spender_user_ids": None, + "sub_project": None, + "updated_at": "2023-11-19T08:06:14.246825+00:00", + }, + ], + "offset": 0, + } + ], +} + +categories_data = { + "create_new_auto_create_category_destination_attributes": [ + { + "Id": "2b3325c2-3f96-46c2-82f6-82c8e018b6a9", + "Status": "OK", + "ProviderName": "Fyle Staging", + "DateTimeUTC": "/Date(1709819683479)/", + "Accounts": [ + { + "AccountID": "9f9cbd27-880e-441e-8cfd-5f59edfec64b", + "Name": "jnjnjnjn", + "Status": "ACTIVE", + "Type": "BANK", + "TaxType": "NONE", + "Class": "ASSET", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountNumber": "123456789012", + "BankAccountType": "BANK", + "CurrencyCode": "INR", + "ReportingCode": "ASS", + "ReportingCodeName": "Assets", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1688713933857+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "3557a0e8-76dd-45b8-b533-b27648cd4a0e", + "Name": "Visa", + "Status": "ACTIVE", + "Type": "BANK", + "TaxType": "NONE", + "Class": "ASSET", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountNumber": "1xxs", + "BankAccountType": "CREDITCARD", + "CurrencyCode": "USD", + "ReportingCode": "ASS", + "ReportingCodeName": "Assets", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1688713933857+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "8f684e1d-f5d0-467a-b9d5-731d591e3bba", + "Code": "121", + "Name": "Bank of Nilesh", + "Status": "ACTIVE", + "Type": "CURRENT", + "TaxType": "NONE", + "Description": "", + "Class": "ASSET", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS", + "ReportingCodeName": "Assets", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1709281044380+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "0dc63626-aa86-466d-8a27-8c7df9a63d96", + "Code": "200", + "Name": "Sales", + "Status": "ACTIVE", + "Type": "REVENUE", + "TaxType": "OUTPUT", + "Description": "Income from any normal business activity", + "Class": "REVENUE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "NONE", + "ReportingCode": "REV.TRA.GOO", + "ReportingCodeName": "Sale of goods", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "79a518db-a622-4321-8045-f3ac126945f9", + "Code": "260", + "Name": "Other Revenue", + "Status": "ACTIVE", + "Type": "REVENUE", + "TaxType": "OUTPUT", + "Description": "Any other income that does not relate to normal business activities and is not recurring", + "Class": "REVENUE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "REV.OTH", + "ReportingCodeName": "Other Revenue", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1685967350977+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "2ad76cde-0717-4da5-9b02-50cfd8576558", + "Code": "270", + "Name": "Interest Income", + "Status": "ACTIVE", + "Type": "REVENUE", + "TaxType": "NONE", + "Description": "Interest income", + "Class": "REVENUE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "REV.INV.INT", + "ReportingCodeName": "Interest", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1686036351337+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "9259b9ff-c332-4a14-9100-d8b1627d5e0b", + "Code": "310", + "Name": "Cost of Goods Sold", + "Status": "ACTIVE", + "Type": "DIRECTCOSTS", + "TaxType": "INPUT", + "Description": "Cost of goods sold by the business", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.COS", + "ReportingCodeName": "Cost of goods sold", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "d9416aab-1702-4b94-9965-4450cb6dc4c1", + "Code": "400", + "Name": "Advertising", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred for advertising while trying to increase sales", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1709278576037+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "7aa95d1d-db87-4448-b4c7-949eb491b021", + "Code": "404", + "Name": "Bank Fees", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Fees charged by your bank for transactions regarding your bank account(s).", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1686042454290+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "bf3dfb1f-8264-4213-9b3c-d969078416e6", + "Code": "408", + "Name": "Cleaning", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred for cleaning business property.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1685951041977+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "b53d9a12-9d2c-4acc-a4a1-dfc36c7001fc", + "Code": "412", + "Name": "Consulting & Accounting", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses related to paying consultants", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.LEG", + "ReportingCodeName": "Professional and consulting fees", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1685695380337+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "edcf8d65-2432-4603-8ad7-b3a515e9edd2", + "Code": "416", + "Name": "Depreciation", + "Status": "ARCHIVED", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "The amount of the asset's cost (based on the useful life) that was consumed during the period", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.DEP", + "ReportingCodeName": "Depreciation", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1686042699667+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "a864ecfb-36d4-4fab-bd8c-0add82abd6d5", + "Code": "420", + "Name": "Entertainment", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Expenses paid by company for the business but are not deductable for income tax purposes.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.ENT", + "ReportingCodeName": "Entertainment", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1686036351407+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "d96a2aa9-44e9-456f-8d33-5f8cf72c42a5", + "Code": "425", + "Name": "Freight & Courier", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred on courier & freight costs", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "5e145416-9910-41d3-99e5-87cf02f19fae", + "Code": "429", + "Name": "General Expenses", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "General expenses related to the running of the business.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "d2dd8a58-f5f2-4214-a1f6-44d1e992876e", + "Code": "433", + "Name": "Insurance Renamed", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred for insuring the business' assets", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.INS", + "ReportingCodeName": "Insurance", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1693209049447+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "0b9653d9-16de-448f-8828-bff4dcb0ad73", + "Code": "437", + "Name": "Interest Expense", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Any interest expenses paid to your tax authority, business bank accounts or credit card accounts.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.INT", + "ReportingCodeName": "Interest and finance charges", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "12b83830-f0a8-4854-a2b6-202da0930c40", + "Code": "441", + "Name": "Legal expenses", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred on any legal matters", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.LEG", + "ReportingCodeName": "Professional and consulting fees", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1685967351070+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "dee1af47-79dc-4695-8c4c-c16ce230f080", + "Code": "445", + "Name": "Light, Power, Heating", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred for lighting, powering or heating the premises", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "c853c6df-ddf7-4a15-a19a-b5dce79cc4b5", + "Code": "449", + "Name": "Motor Vehicle Expenses", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred on the running of company motor vehicles", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.VEH", + "ReportingCodeName": "Vehicle expenses", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "57f37702-a707-4e92-9844-333da206a9dc", + "Code": "453", + "Name": "Office Expenses", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "General expenses related to the running of the business office.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "14ad8fbe-36aa-4293-aafa-1dcc458dd78d", + "Code": "461", + "Name": "Printing & Stationery", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred by the entity as a result of printing and stationery", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "20725a89-25a4-4b7d-91c6-b30720f5edee", + "Code": "469", + "Name": "Rent", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "The payment to lease a building or area.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.REN", + "ReportingCodeName": "Rental and lease payments", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1686036351467+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "627d72a2-6ba7-4c0a-922d-ac0d7082f016", + "Code": "473", + "Name": "Repairs and Maintenance", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred on a damaged or run down asset that will bring the asset back to its original condition.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.REP", + "ReportingCodeName": "Repairs and maintenance", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "ab99c95b-0a2a-4c34-9077-414080325f2a", + "Code": "477", + "Name": "Wages and Salaries", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Payment to employees in exchange for their resources", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.EMP", + "ReportingCodeName": "Employment costs", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "1d4092c5-1029-4403-a6e1-e97dd87cdc78", + "Code": "478", + "Name": "Superannuation", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Superannuation contributions", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.EMP.SUP", + "ReportingCodeName": "Superannuation expenses", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "e9f14f1f-6a84-4cd8-b79d-8f7ab8b0c1b6", + "Code": "485", + "Name": "Subscriptions", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "E.g. Magazines, professional bodies", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "ec9d352a-12c6-48c4-bb52-4dfeeb83162d", + "Code": "489", + "Name": "Telephone & Internet", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenditure incurred from any business-related phone calls, phone lines, or internet connections", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP", + "ReportingCodeName": "Expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "2ca76be0-14b8-4366-8211-5826e6fdba99", + "Code": "493", + "Name": "Travel - National", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "INPUT", + "Description": "Expenses incurred from domestic travel which has a business purpose", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.TRA", + "ReportingCodeName": "Travel and accommodation", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "4b1736f3-ba9f-4efe-902b-b31406f0fd76", + "Code": "494", + "Name": "Travel - International", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Expenses incurred from international travel which has a business purpose", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.TRA", + "ReportingCodeName": "Travel and accommodation", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "dca00e8b-052d-4eda-8baa-cf8cc7f0d6d8", + "Code": "497", + "Name": "Bank Revaluations", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Bank account revaluations due for foreign exchange rate changes", + "Class": "EXPENSE", + "SystemAccount": "BANKCURRENCYGAIN", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.FOR", + "ReportingCodeName": "Foreign currency gains and losses", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1667915233150+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "9d5faca4-ef3f-4909-8f8a-73a3f21ece07", + "Code": "498", + "Name": "Unrealised Currency Gains", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Unrealised currency gains on outstanding items", + "Class": "EXPENSE", + "SystemAccount": "UNREALISEDCURRENCYGAIN", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.FOR.UGL", + "ReportingCodeName": "Unrealised foreign currency gains and losses", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1667915233150+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "65e9d59c-aa4a-442b-a02e-d3d31a591168", + "Code": "499", + "Name": "Realised Currency Gains", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "Gains or losses made due to currency exchange rate changes", + "Class": "EXPENSE", + "SystemAccount": "REALISEDCURRENCYGAIN", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.FOR.RGL", + "ReportingCodeName": "Realised foreign currency gains and losses", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1667915233150+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "8576f8f3-0992-4dae-964c-2e4edb2d59ae", + "Code": "505", + "Name": "Income Tax Expense", + "Status": "ACTIVE", + "Type": "EXPENSE", + "TaxType": "NONE", + "Description": "A percentage of total earnings paid to the government.", + "Class": "EXPENSE", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EXP.INC", + "ReportingCodeName": "Income tax expense", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "33f52161-f951-4316-adc2-f157ca4d00f9", + "Code": "610", + "Name": "Accounts Receivable", + "Status": "ACTIVE", + "Type": "CURRENT", + "TaxType": "NONE", + "Description": "Outstanding invoices the company has issued out to the client but has not yet received in cash at balance date.", + "Class": "ASSET", + "SystemAccount": "DEBTORS", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.CUR.REC.TRA", + "ReportingCodeName": "Trade receivables", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "46fd84a2-5a17-4187-960e-cf7d1f6181ae", + "Code": "620", + "Name": "Prepayments", + "Status": "ACTIVE", + "Type": "CURRENT", + "TaxType": "NONE", + "Description": "An expenditure that has been paid for in advance.", + "Class": "ASSET", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.CUR.REC.PRE", + "ReportingCodeName": "Prepayments", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "3f5669af-773d-4d37-8a93-beabae32ee42", + "Code": "630", + "Name": "Inventory", + "Status": "ACTIVE", + "Type": "INVENTORY", + "TaxType": "NONE", + "Description": "Value of tracked items for resale.", + "Class": "ASSET", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.CUR.INY", + "ReportingCodeName": "Inventories", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "3e71ed7a-be17-4089-b8dd-caf6f3a572e5", + "Code": "710", + "Name": "Office Equipment", + "Status": "ACTIVE", + "Type": "FIXED", + "TaxType": "INPUT", + "Description": "Office equipment that is owned and controlled by the business", + "Class": "ASSET", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.NCA.FIX.OWN.PLA", + "ReportingCodeName": "Plant and Machinery owned", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "cbc86a0c-f852-4571-a39e-471817b73e1b", + "Code": "711", + "Name": "Less Accumulated Depreciation on Office Equipment", + "Status": "ACTIVE", + "Type": "FIXED", + "TaxType": "NONE", + "Description": "The total amount of office equipment cost that has been consumed by the entity (based on the useful life)", + "Class": "ASSET", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.NCA.FIX.OWN.PLA.ACC", + "ReportingCodeName": "Accumulated depreciation - plant and machinery owned", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "530d29b0-dcd2-48b3-8ac8-0e90cbcebdab", + "Code": "720", + "Name": "Computer Equipment", + "Status": "ACTIVE", + "Type": "FIXED", + "TaxType": "INPUT", + "Description": "Computer equipment that is owned and controlled by the business", + "Class": "ASSET", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.NCA.FIX.OWN.PLA", + "ReportingCodeName": "Plant and Machinery owned", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "02d32937-be97-4cee-b11e-08aeefd55014", + "Code": "721", + "Name": "Less Accumulated Depreciation on Computer Equipment", + "Status": "ACTIVE", + "Type": "FIXED", + "TaxType": "NONE", + "Description": "The total amount of computer equipment cost that has been consumed by the business (based on the useful life)", + "Class": "ASSET", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "ASS.NCA.FIX.OWN.PLA.ACC", + "ReportingCodeName": "Accumulated depreciation - plant and machinery owned", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "2308e46a-a1cc-44bf-9e5c-084eb0de6bec", + "Code": "800", + "Name": "Accounts Payable", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "Outstanding invoices the company has received from suppliers but has not yet paid at balance date", + "Class": "LIABILITY", + "SystemAccount": "CREDITORS", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.PAY.TRA", + "ReportingCodeName": "Trade payables", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "68d30c24-ad98-4776-a4e6-995a529e3cc4", + "Code": "801", + "Name": "Unpaid Expense Claims", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "Expense claims typically made by employees/shareholder employees still outstanding.", + "Class": "LIABILITY", + "SystemAccount": "UNPAIDEXPCLM", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.PAY.EMP", + "ReportingCodeName": "Employee entitlements (wages, annual leave, etc)", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "e74b49cc-412b-46ee-b8a2-025ef248230a", + "Code": "803", + "Name": "Wages Payable", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "Xero automatically updates this account for payroll entries created using Payroll and will store the payroll amount to be paid to the employee for the pay run. This account enables you to maintain separate accounts for employee Wages Payable amounts and Accounts Payable amounts", + "Class": "LIABILITY", + "SystemAccount": "WAGEPAYABLES", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.PAY.EMP", + "ReportingCodeName": "Employee entitlements (wages, annual leave, etc)", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "4cba4d8a-36ee-4b63-a3e0-da7897396eea", + "Code": "820", + "Name": "Sales Tax", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "The balance in this account represents Sales Tax owing to or from your tax authority. At the end of the tax period, it is this account that should be used to code against either the 'refunds from' or 'payments to' your tax authority that will appear on the bank statement. Xero has been designed to use only one sales tax account to track sales taxes on income and expenses, so there is no need to add any new sales tax accounts to Xero.", + "Class": "LIABILITY", + "SystemAccount": "GST", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.TAX.VAT", + "ReportingCodeName": "Value added tax", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "b6e85be7-2dc7-401d-b824-2a22788d071c", + "Code": "825", + "Name": "Employee Tax Payable", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "The amount of tax that has been deducted from wages or salaries paid to employes and is due to be paid", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.PAY.PAY", + "ReportingCodeName": "PAYE", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "82cc7e01-e95e-47d5-921e-d825b187abec", + "Code": "826", + "Name": "Superannuation Payable", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "The amount of superannuation that is due to be paid", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.PAY.EMP", + "ReportingCodeName": "Employee entitlements (wages, annual leave, etc)", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "1eb2fa60-364e-493a-9bf1-9d7eecc2efd0", + "Code": "830", + "Name": "Income Tax Payable", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "The amount of income tax that is due to be paid, also resident withholding tax paid on interest received.", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR.TAX.INC", + "ReportingCodeName": "Income tax", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "30a791d0-94b7-4d8b-a02c-ec876aff445f", + "Code": "840", + "Name": "Historical Adjustment", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "For accountant adjustments", + "Class": "LIABILITY", + "SystemAccount": "HISTORICAL", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR", + "ReportingCodeName": "Current liabilities", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "6cb7924f-c295-49c1-ac31-a47b78784220", + "Code": "850", + "Name": "Suspense", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "An entry that allows an unknown transaction to be entered, so the accounts can still be worked on in balance and the entry can be dealt with later.", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR", + "ReportingCodeName": "Current liabilities", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "936770fa-1201-42fc-84a8-6b90fc9d18c7", + "Code": "860", + "Name": "Rounding", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "An adjustment entry to allow for rounding", + "Class": "LIABILITY", + "SystemAccount": "ROUNDING", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR", + "ReportingCodeName": "Current liabilities", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "e101a624-ed57-4063-a0f8-c11f22a9a8d1", + "Code": "877", + "Name": "Tracking Transfers", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "Transfers between tracking categories", + "Class": "LIABILITY", + "SystemAccount": "TRACKINGTRANSFERS", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.CUR", + "ReportingCodeName": "Current liabilities", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "44daf44a-efa8-4783-9fd2-1013f96aa016", + "Code": "880", + "Name": "Owner A Drawings", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "Withdrawals by the owners", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": True, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EQU.DRA", + "ReportingCodeName": "Owners/partners drawings", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "358cc8c3-44c1-40af-aa80-3642ee6c410f", + "Code": "881", + "Name": "Owner A Funds Introduced", + "Status": "ACTIVE", + "Type": "CURRLIAB", + "TaxType": "NONE", + "Description": "Funds contributed by the owner", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": True, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EQU.ADV", + "ReportingCodeName": "Owners/partners funds introduced", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "63ac4208-7856-4cef-b5ea-3f048f2a0d21", + "Code": "900", + "Name": "Loan", + "Status": "ACTIVE", + "Type": "TERMLIAB", + "TaxType": "NONE", + "Description": "Money that has been borrowed from a creditor", + "Class": "LIABILITY", + "SystemAccount": "", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "LIA.NCL.LOA", + "ReportingCodeName": "Loans (non current)", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "0af3c858-8972-4370-b5ad-4af231d40e57", + "Code": "960", + "Name": "Retained Earnings", + "Status": "ACTIVE", + "Type": "EQUITY", + "TaxType": "NONE", + "Description": "Do not Use", + "Class": "EQUITY", + "SystemAccount": "RETAINEDEARNINGS", + "EnablePaymentsToAccount": False, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EQU.RET", + "ReportingCodeName": "Retained earnings", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + { + "AccountID": "66889407-4a9b-410b-8862-6b1587ebf322", + "Code": "970", + "Name": "Owner A Share Capital", + "Status": "ACTIVE", + "Type": "EQUITY", + "TaxType": "NONE", + "Description": "The value of shares purchased by the shareholders", + "Class": "EQUITY", + "SystemAccount": "", + "EnablePaymentsToAccount": True, + "ShowInExpenseClaims": False, + "BankAccountType": "", + "ReportingCode": "EQU.SHA", + "ReportingCodeName": "Share capital", + "HasAttachments": False, + "UpdatedDateUTC": "/Date(1648420299263+0000)/", + "AddToWatchlist": False, + }, + ], + } + ], + "create_new_auto_create_categories_expense_attributes_0": [ + { + "count": 1, + "data": [ + { + "approver_user_ids": [], + "approver_users": [], + "category_ids": None, + "code": "235", + "created_at": "2023-11-19T08:03:34.144823+00:00", + "description": "Sage Intacct Project - David, Id - 235", + "display_name": "David", + "id": 324522, + "is_enabled": True, + "name": "David", + "org_id": "or5qYLrvnoF9", + "restricted_spender_user_ids": None, + "sub_project": None, + "updated_at": "2023-11-19T08:06:14.246825+00:00", + }, + ], + "offset": 0, + } + ], +} diff --git a/tests/test_fyle_integrations_imports/helpers.py b/tests/test_fyle_integrations_imports/helpers.py new file mode 100644 index 00000000..53239d8f --- /dev/null +++ b/tests/test_fyle_integrations_imports/helpers.py @@ -0,0 +1,37 @@ +from fyle_integrations_imports.modules.base import Base +from fyle_integrations_platform_connector import PlatformConnector +from apps.workspaces.models import FyleCredential, XeroCredentials +from apps.xero.utils import XeroConnector +from typing import List + + +def get_base_class_instance( + workspace_id: int = 1, + source_field: str = 'PROJECT', + destination_field: str = 'PROJECT', + platform_class_name: str = 'projects', + sync_after: str = None, + destination_sync_methods: List[str] = ['customers'] +): + + xero_credentials = XeroCredentials.get_active_xero_credentials(workspace_id) + xero_connection = XeroConnector(credentials_object=xero_credentials, workspace_id=workspace_id) + + base = Base( + workspace_id = workspace_id, + source_field = source_field, + destination_field = destination_field, + platform_class_name = platform_class_name, + sync_after = sync_after, + sdk_connection = xero_connection, + destination_sync_methods = destination_sync_methods + ) + + return base + + +def get_platform_connection(workspace_id): + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentials=fyle_credentials) + + return platform diff --git a/tests/test_fyle_integrations_imports/test_modules/test_base.py b/tests/test_fyle_integrations_imports/test_modules/test_base.py new file mode 100644 index 00000000..82a681ae --- /dev/null +++ b/tests/test_fyle_integrations_imports/test_modules/test_base.py @@ -0,0 +1,274 @@ +from datetime import ( + datetime, + timezone, + timedelta +) +from fyle_accounting_mappings.models import ( + DestinationAttribute, + ExpenseAttribute, + Mapping +) +from unittest import mock +from apps.workspaces.models import Workspace +from apps.workspaces.models import XeroCredentials +from apps.xero.utils import XeroConnector +from apps.tasks.models import Error +from fyle_integrations_imports.modules.projects import Project +from fyle_integrations_imports.modules.categories import Category +from fyle_integrations_imports.models import ImportLog +from tests.test_fyle_integrations_imports.helpers import ( + get_base_class_instance, + get_platform_connection +) +from tests.test_fyle_integrations_imports.fixtures import projects_data, categories_data + + +def test_remove_duplicates(db): + attributes = DestinationAttribute.objects.filter(attribute_type='CUSTOMER') + + assert len(attributes) == 14 + + for attribute in attributes: + DestinationAttribute.objects.create( + attribute_type='CUSTOMER', + workspace_id=attribute.workspace_id, + value=attribute.value, + destination_id='010{0}'.format(attribute.destination_id) + ) + + attributes = DestinationAttribute.objects.filter(attribute_type='CUSTOMER') + + assert len(attributes) == 28 + + base = get_base_class_instance() + + attributes = base.remove_duplicate_attributes(attributes) + assert len(attributes) == 14 + + +def test_get_platform_class(db): + base = get_base_class_instance() + platform = get_platform_connection(1) + + assert base.get_platform_class(platform) == platform.projects + + base = get_base_class_instance(workspace_id=1, source_field='CATEGORY', destination_field='ACCOUNT', platform_class_name='categories') + assert base.get_platform_class(platform) == platform.categories + + base = get_base_class_instance(workspace_id=1, source_field='COST_CENTER', destination_field='DEPARTMENT', platform_class_name='cost_centers') + assert base.get_platform_class(platform) == platform.cost_centers + + +def test_construct_attributes_filter(db): + paginated_destination_attribute_values = ['Mobile App Redesign', 'Platform APIs', 'Fyle NetSuite Integration', 'Fyle Sage Intacct Integration', 'Support Taxes', 'T&M Project with Five Tasks', 'Fixed Fee Project with Five Tasks', 'General Overhead', 'General Overhead-Current', 'Youtube proj', 'Integrations', 'Yujiro', 'Pickle'] + base = get_base_class_instance() + + assert base.construct_attributes_filter('PROJECT', False) == {'attribute_type': 'PROJECT', 'workspace_id': 1} + + date_string = '2023-08-06 12:50:05.875029' + sync_after = datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S.%f') + + base = get_base_class_instance(workspace_id=1, source_field='COST_CENTER', destination_field='CUSTOMER', platform_class_name='cost_centers', sync_after=sync_after) + + filters = base.construct_attributes_filter('COST_CENTER', False, paginated_destination_attribute_values) + + assert filters == {'attribute_type': 'COST_CENTER', 'workspace_id': 1, 'value__in': paginated_destination_attribute_values} + + filters = base.construct_attributes_filter('CUSTOMER', True, paginated_destination_attribute_values) + + assert filters == {'attribute_type': 'CUSTOMER', 'workspace_id': 1, 'updated_at__gte': sync_after, 'value__in': paginated_destination_attribute_values} + + +def test_expense_attributes_sync_after(db, create_temp_workspace, add_xero_credentials, create_project_mapping): + project = get_base_class_instance(workspace_id=3) + + current_time = datetime.now() - timedelta(minutes=300) + sync_after = current_time.replace(tzinfo=timezone.utc) + project.sync_after = sync_after + + expense_attributes = ExpenseAttribute.objects.filter(workspace_id=3, attribute_type='PROJECT')[0:1] + + assert expense_attributes.count() == 1 + + paginated_expense_attribute_values = [] + + for expense_attribute in expense_attributes: + expense_attribute.updated_at = datetime.now().replace(tzinfo=timezone.utc) + expense_attribute.save() + paginated_expense_attribute_values.append(expense_attribute.value) + + filters = project.construct_attributes_filter('PROJECT', paginated_expense_attribute_values) + + expense_attributes = ExpenseAttribute.objects.filter(**filters) + + assert expense_attributes.count() == 1 + + +def test_auto_create_destination_attributes(mocker, db, test_connection, create_temp_workspace, add_xero_credentials, add_fyle_credentials, add_tenant_mapping): + workspace_id = 3 + xero_credentials = XeroCredentials.get_active_xero_credentials(workspace_id) + xero_connection = XeroConnector(credentials_object=xero_credentials, workspace_id=workspace_id) + project = Project(3, 'CUSTOMER', None, xero_connection, ['customers'], True) + project.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='PROJECT').delete() + Mapping.objects.filter(workspace_id=workspace_id, destination_type='CUSTOMER').delete() + DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CUSTOMER').delete() + ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='PROJECT').delete() + + with mock.patch('fyle.platform.apis.v1beta.admin.Projects.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.Projects.post_bulk', + return_value=[] + ) + mocker.patch( + 'xerosdk.apis.Contacts.get_all', + return_value=projects_data['create_new_auto_create_projects_destination_attributes'] + ) + mock_call.side_effect = [ + projects_data['create_new_auto_create_projects_expense_attributes_0'] + ] + project.trigger_import() + + # Not creating the schedule part due to time diff + current_time = datetime.now() + sync_after = current_time.replace(tzinfo=timezone.utc) + project.sync_after = sync_after + + import_log = ImportLog.objects.filter(workspace_id=workspace_id).first() + import_log.status = 'COMPLETE' + import_log.attribute_type = 'PROJECT' + import_log.total_batches_count = 10 + import_log.processed_batches_count = 10 + import_log.error_log = [] + import_log.save() + + import_log = ImportLog.objects.filter(workspace_id=workspace_id).first() + + response = project.trigger_import() + + import_log_post_run = ImportLog.objects.filter(workspace_id=workspace_id).first() + + assert response == None + assert import_log.status == import_log_post_run.status + assert import_log.total_batches_count == import_log_post_run.total_batches_count + + # not creating the schedule due to a schedule running already + project.sync_after = None + + import_log = ImportLog.objects.filter(workspace_id=workspace_id).first() + import_log.status = 'IN_PORGRESS' + import_log.total_batches_count = 8 + import_log.processed_batches_count = 3 + import_log.save() + + response = project.trigger_import() + + assert response == None + assert import_log.status == 'IN_PORGRESS' + assert import_log.total_batches_count != 0 + assert import_log.processed_batches_count != 0 + + # not creating due to no destination_attributes(projects) + Mapping.objects.filter(workspace_id=workspace_id, source_type='PROJECT').delete() + Mapping.objects.filter(workspace_id=workspace_id, destination_type='CUSTOMER').delete() + DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='CUSTOMER').delete() + ExpenseAttribute.objects.filter(workspace_id=workspace_id, attribute_type='PROJECT').delete() + + with mock.patch('fyle.platform.apis.v1beta.admin.Projects.list_all') as mock_call: + mocker.patch( + 'fyle_integrations_platform_connector.apis.Projects.post_bulk', + return_value=[] + ) + mocker.patch( + 'xerosdk.apis.Contacts.get_all', + return_value=[] + ) + mock_call.side_effect = [ + projects_data['create_new_auto_create_projects_expense_attributes_0'] + ] + project.trigger_import() + + import_log = ImportLog.objects.filter(workspace_id=workspace_id).first() + + assert import_log.total_batches_count == 0 + assert import_log.processed_batches_count == 0 + + # not creating due to no destination_attributes(categories) + workspace_id = 3 + 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() + + category = Category(3, 'ACCOUNT', None, xero_connection, ['items'], True, False, ['Expense', 'Fixed Asset']) + 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('xerosdk.apis.Items.get_all',return_value=[]) + mocker.patch( + 'xerosdk.apis.Accounts.get_all', + return_value=[] + ) + mock_call.side_effect = [ + categories_data['create_new_auto_create_categories_expense_attributes_0'] + ] + category.trigger_import() + + import_log = ImportLog.objects.filter(workspace_id=workspace_id).first() + + assert import_log.total_batches_count == 0 + assert import_log.processed_batches_count == 0 + + +def test_resolve_expense_attribute_errors(db, create_temp_workspace, add_xero_credentials, create_category_mapping): + workspace_id = 3 + xero_credentials = XeroCredentials.get_active_xero_credentials(workspace_id) + xero_connection = XeroConnector(credentials_object=xero_credentials, workspace_id=workspace_id) + category = Category(workspace_id, 'ACCOUNT', None, xero_connection, ['accounts'], True, False, ['Expense', 'Fixed Asset']) + + # deleting all the Error objects + Error.objects.filter(workspace_id=workspace_id).delete() + + # getting the expense_attribute + source_category = ExpenseAttribute.objects.filter( + source_id='src123', + workspace_id=workspace_id, + attribute_type='CATEGORY' + ).first() + + Mapping.objects.filter(workspace_id=workspace_id, source_id=source_category.id).delete() + + category_mapping_count = Mapping.objects.filter(workspace_id=workspace_id, source_id=source_category.id).count() + + assert category_mapping_count == 0 + + error = Error.objects.create( + workspace_id=workspace_id, + expense_attribute=source_category, + type='CATEGORY_MAPPING', + error_title=source_category.value, + error_detail='Category mapping is missing', + is_resolved=False + ) + + assert Error.objects.get(id=error.id).is_resolved == False + + destination_attribute = DestinationAttribute.objects.filter(workspace_id=workspace_id, attribute_type='ACCOUNT').first() + + category_list = [] + category_list.append( + Mapping( + workspace_id=workspace_id, + source_id=source_category.id, + destination_id=destination_attribute.id + ) + ) + Mapping.objects.bulk_create(category_list) + + category.resolve_expense_attribute_errors() + assert Error.objects.get(id=error.id).is_resolved == True diff --git a/tests/test_fyle_integrations_imports/test_queue.py b/tests/test_fyle_integrations_imports/test_queue.py new file mode 100644 index 00000000..e57e7dc4 --- /dev/null +++ b/tests/test_fyle_integrations_imports/test_queue.py @@ -0,0 +1,60 @@ +from fyle_integrations_imports.queues import chain_import_fields_to_fyle +from fyle_integrations_imports.dataclasses import TaskSetting + + +def test_chain_import_fields_to_fyle(mocker): + mocker.patch('fyle_integrations_imports.tasks.trigger_import_via_schedule') + mock_chain = mocker.patch('fyle_integrations_imports.queues.Chain') + mock_chain.return_value.length().__gt__.return_value = 1 + + task_settings: TaskSetting = { + 'sdk_connection_string': 'sdk_connection_string', + 'credentials': 'credentials', + 'custom_properties': { + 'func': 'fyle_integrations_imports.tasks.trigger_import_via_schedule', + 'args': { + 'workspace_id': 1, + 'destination_field': 'CUSTOM_FIELD', + 'object_type': 'CUSTOM_FIELD', + 'destination_sync_methods': ['destination_sync_methods'], + 'is_auto_sync_enabled': False, + 'is_3d_mapping': False, + } + }, + 'import_categories': { + 'destination_field': 'CATEGORY', + 'destination_sync_methods': ['destination_sync_methods'], + 'is_auto_sync_enabled': True, + 'is_3d_mapping': False, + 'charts_of_accounts': 'charts_of_accounts' + }, + 'import_tax': { + 'destination_field': 'TAX_GROUP', + 'destination_sync_methods': ['destination_sync_methods'], + 'is_auto_sync_enabled': False, + 'is_3d_mapping': False + }, + 'import_vendors_as_merchants': { + 'destination_field': 'MERCHANT', + 'destination_sync_methods': ['destination_sync_methods'], + 'is_auto_sync_enabled': False, + 'is_3d_mapping': False + }, + 'mapping_settings': [ + { + 'source_field': 'source_field', + 'destination_field': 'destination_field', + 'is_3d_mapping': False, + 'destination_sync_methods': ['destination_sync_methods'], + 'is_custom': False + } + ], + "import_items": { + "destination_field": "CATEGORY", + "destination_sync_methods": ["destination_sync_methods"], + "is_auto_sync_enabled": True, + "is_3d_mapping": False + } + } + + chain_import_fields_to_fyle(1, task_settings) diff --git a/tests/test_mappings/conftest.py b/tests/test_mappings/conftest.py index 14dc866c..30a507a4 100644 --- a/tests/test_mappings/conftest.py +++ b/tests/test_mappings/conftest.py @@ -1,5 +1,6 @@ import pytest from fyle_accounting_mappings.models import DestinationAttribute, MappingSetting +from apps.workspaces.models import Workspace, XeroCredentials, FyleCredential @pytest.fixture @@ -26,3 +27,39 @@ def create_mapping_setting(db): 1, True, ) + + +@pytest.fixture +def create_temp_workspace(db): + workspace_id = 3 + Workspace.objects.create( + id=workspace_id, + name="Fyle for Hrishabh Testing", + fyle_org_id="Testing123", + xero_short_code="xero123", + last_synced_at=None, + source_synced_at=None, + destination_synced_at=None, + xero_accounts_last_synced_at=None + ) + + +@pytest.fixture +def add_xero_credentials(db, create_temp_workspace): + workspace_id = 3 + XeroCredentials.objects.create( + workspace_id=workspace_id, + refresh_token="refresh_token", + is_expired=False, + ) + + +@pytest.fixture() +def add_fyle_credentials(db): + workspace_id = 3 + + FyleCredential.objects.create( + refresh_token="refresh_token", + workspace_id=workspace_id, + cluster_domain="https://staging.fyle.tech", + ) diff --git a/tests/test_mappings/test_exceptions.py b/tests/test_mappings/test_exceptions.py new file mode 100644 index 00000000..61e1ae2c --- /dev/null +++ b/tests/test_mappings/test_exceptions.py @@ -0,0 +1,109 @@ +from fyle_integrations_imports.models import ImportLog +from fyle_integrations_imports.modules.projects import Project +from apps.workspaces.models import XeroCredentials +from apps.xero.utils import XeroConnector +from apps.mappings.exceptions import handle_import_exceptions_v2 +from fyle.platform.exceptions import InternalServerError, InvalidTokenError, WrongParamsError +from xerosdk.exceptions import InvalidTokenError as XeroInvalidTokenError +from xerosdk.exceptions import WrongParamsError as XeroWrongParamsError + + +def test_handle_import_exceptions(db, create_temp_workspace, add_xero_credentials, add_fyle_credentials): + workspace_id = 3 + ImportLog.objects.create( + workspace_id=workspace_id, + status = 'IN_PROGRESS', + attribute_type = 'PROJECT', + total_batches_count = 10, + processed_batches_count = 2, + error_log = [] + ) + xero_credentials = XeroCredentials.get_active_xero_credentials(workspace_id) + xero_connection = XeroConnector(credentials_object=xero_credentials, workspace_id=workspace_id) + + import_log = ImportLog.objects.get(workspace_id=workspace_id, attribute_type='PROJECT') + project = Project(workspace_id, 'CUSTOMER', None, xero_connection, 'customers', True) + + # WrongParamsError + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise WrongParamsError('This is WrongParamsError') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FAILED' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'This is WrongParamsError' + assert import_log.error_log['alert'] == True + + # FyleInvalidTokenError + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise InvalidTokenError('This is FyleInvalidTokenError') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FAILED' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'Invalid Token for fyle' + assert import_log.error_log['alert'] == False + + # InternalServerError + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise InternalServerError('This is InternalServerError') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FAILED' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'Internal server error while importing to Fyle' + assert import_log.error_log['alert'] == True + + # XeroWrongParamsError + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise XeroWrongParamsError('This is InternalServerError') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FAILED' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'Invalid Token or Xero credentials does not exist workspace_id - 3' + assert import_log.error_log['alert'] == False + + # XeroInvalidTokenError + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise XeroInvalidTokenError('This is InternalServerError') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FAILED' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'Invalid Token or Xero credentials does not exist workspace_id - 3' + assert import_log.error_log['alert'] == False + + # XeroCredentials.DoesNotExist + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise XeroCredentials.DoesNotExist('This is InternalServerError') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FAILED' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'Invalid Token or Xero credentials does not exist workspace_id - 3' + assert import_log.error_log['alert'] == False + + # Exception + @handle_import_exceptions_v2 + def to_be_decoreated(expense_attribute_instance, import_log): + raise Exception('This is a general Exception') + + to_be_decoreated(project, import_log) + + assert import_log.status == 'FATAL' + assert import_log.error_log['task'] == 'Import PROJECT to Fyle and Auto Create Mappings' + assert import_log.error_log['message'] == 'Something went wrong' + assert import_log.error_log['alert'] == False diff --git a/tests/test_mappings/test_helpers.py b/tests/test_mappings/test_helpers.py index a36cab73..80eec886 100644 --- a/tests/test_mappings/test_helpers.py +++ b/tests/test_mappings/test_helpers.py @@ -1,7 +1,7 @@ import pytest from fyle_accounting_mappings.models import MappingSetting -from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks +from apps.mappings.schedules import new_schedule_or_delete_fyle_import_tasks from apps.workspaces.models import WorkspaceGeneralSettings @@ -10,19 +10,37 @@ def configuration(db): return WorkspaceGeneralSettings(workspace_id=1, import_categories=False) -def test_schedule_or_delete_fyle_import_tasks_with_no_configuration(configuration): - schedule_or_delete_fyle_import_tasks(configuration) +@pytest.fixture +def mapping_settings(): + mapping_setting = [ + { + "source_field": "CATEGORY", + "workspace_id": 1, + "import_to_fyle": True + }, + { + "source_field": "PROJECT", + "workspace_id": 1, + "import_to_fyle": False + } + ] + + return mapping_setting + + +def test_schedule_or_delete_fyle_import_tasks_with_no_configuration(configuration, mapping_settings): + new_schedule_or_delete_fyle_import_tasks(configuration, mapping_settings) -def test_schedule_or_delete_fyle_import_tasks_with_import_categories(configuration, db): +def test_schedule_or_delete_fyle_import_tasks_with_import_categories(configuration, mapping_settings): configuration.import_categories = True - schedule_or_delete_fyle_import_tasks(configuration) + new_schedule_or_delete_fyle_import_tasks(configuration, mapping_settings) -def test_schedule_or_delete_fyle_import_tasks_with_project_mapping(configuration, db): +def test_schedule_or_delete_fyle_import_tasks_with_project_mapping(configuration, mapping_settings): project_mapping = MappingSetting.objects.create( source_field="PROJECT", workspace_id=1, import_to_fyle=True ) configuration.import_categories = False - schedule_or_delete_fyle_import_tasks(configuration) + new_schedule_or_delete_fyle_import_tasks(configuration, mapping_settings) project_mapping.delete() diff --git a/tests/test_mappings/test_queues.py b/tests/test_mappings/test_queues.py new file mode 100644 index 00000000..b0e76788 --- /dev/null +++ b/tests/test_mappings/test_queues.py @@ -0,0 +1,24 @@ +from apps.mappings.queue import construct_tasks_and_chain_import_fields_to_fyle +from apps.workspaces.models import WorkspaceGeneralSettings + + +def test_construct_tasks_and_chain_import_fields_to_fyle( + db, + mocker, + create_temp_workspace, + add_xero_credentials, + create_mapping_setting +): + workspace_id = 3 + mocker.patch('apps.mappings.queue.chain_import_fields_to_fyle') + + WorkspaceGeneralSettings.objects.create( + workspace_id=workspace_id, + import_suppliers_as_merchants=True, + import_categories=True, + import_tax_codes=True, + charts_of_accounts=['Income'], + import_customers=True + ) + + construct_tasks_and_chain_import_fields_to_fyle(workspace_id) diff --git a/tests/test_mappings/test_signals.py b/tests/test_mappings/test_signals.py index d218477f..de4436c6 100644 --- a/tests/test_mappings/test_signals.py +++ b/tests/test_mappings/test_signals.py @@ -32,11 +32,11 @@ def test_run_post_mapping_settings_triggers(db, mocker, test_connection): mapping_setting.save() schedule = Schedule.objects.filter( - func="apps.mappings.tasks.auto_create_project_mappings", + func="apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle", args="{}".format(workspace_id), ).first() - assert schedule.func == "apps.mappings.tasks.auto_create_project_mappings" + assert schedule.func == "apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle" assert schedule.args == "1" mapping_setting = MappingSetting( @@ -49,11 +49,11 @@ def test_run_post_mapping_settings_triggers(db, mocker, test_connection): mapping_setting.save() schedule = Schedule.objects.filter( - func="apps.mappings.tasks.auto_create_cost_center_mappings", + func="apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle", args="{}".format(workspace_id), ).first() - assert schedule.func == "apps.mappings.tasks.auto_create_cost_center_mappings" + assert schedule.func == "apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle" assert schedule.args == "1" mapping_setting = MappingSetting( @@ -66,12 +66,12 @@ def test_run_post_mapping_settings_triggers(db, mocker, test_connection): mapping_setting.save() schedule = Schedule.objects.filter( - func="apps.mappings.tasks.async_auto_create_custom_field_mappings", + func="apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle", args="{}".format(workspace_id), ).first() assert ( - schedule.func == "apps.mappings.tasks.async_auto_create_custom_field_mappings" + schedule.func == "apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle" ) assert schedule.args == "1" @@ -95,11 +95,11 @@ def test_run_post_mapping_settings_triggers(db, mocker, test_connection): mapping_setting.save() schedule = Schedule.objects.filter( - func="apps.mappings.tasks.auto_import_and_map_fyle_fields", + func="apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle", args="{}".format(workspace_id), ).first() - assert schedule.func == "apps.mappings.tasks.auto_import_and_map_fyle_fields" + assert schedule.func == "apps.mappings.queue.construct_tasks_and_chain_import_fields_to_fyle" def test_run_pre_mapping_settings_triggers(db, mocker, test_connection): diff --git a/tests/test_mappings/test_tasks.py b/tests/test_mappings/test_tasks.py index e9448f29..801b9b3b 100644 --- a/tests/test_mappings/test_tasks.py +++ b/tests/test_mappings/test_tasks.py @@ -1,319 +1,29 @@ from unittest import mock -import pytest from django_q.models import Schedule from fyle.platform.exceptions import InternalServerError from fyle.platform.exceptions import InvalidTokenError as FyleInvalidTokenError from fyle_accounting_mappings.models import ( - CategoryMapping, - DestinationAttribute, EmployeeMapping, ExpenseAttribute, - Mapping, - MappingSetting, + Mapping ) -from fyle_integrations_platform_connector import PlatformConnector -from xerosdk.exceptions import UnsuccessfulAuthentication, WrongParamsError +from xerosdk.exceptions import UnsuccessfulAuthentication from apps.fyle.models import ExpenseGroup from apps.mappings.queue import ( - schedule_auto_map_employees, - schedule_cost_centers_creation, - schedule_fyle_attributes_creation, - schedule_tax_groups_creation, + schedule_auto_map_employees ) from apps.mappings.tasks import ( - async_auto_create_custom_field_mappings, async_auto_map_employees, - auto_create_category_mappings, - auto_create_cost_center_mappings, - auto_create_expense_fields_mappings, - auto_create_project_mappings, - auto_create_tax_codes_mappings, - auto_import_and_map_fyle_fields, - create_fyle_categories_payload, - get_all_categories_from_fyle, - remove_duplicates, - resolve_expense_attribute_errors, - upload_categories_to_fyle, + resolve_expense_attribute_errors ) from apps.tasks.models import Error -from apps.workspaces.models import FyleCredential, WorkspaceGeneralSettings, XeroCredentials -from tests.helper import dict_compare_keys +from apps.workspaces.models import WorkspaceGeneralSettings, XeroCredentials from tests.test_fyle.fixtures import data as fyle_data -from tests.test_mappings.fixtures import data from tests.test_xero.fixtures import data as xero_data -def test_auto_create_tax_codes_mappings(db, mocker): - workspace_id = 1 - mocker.patch( - "fyle_integrations_platform_connector.apis.TaxGroups.post_bulk", return_value=[] - ) - - mocker.patch( - "fyle_integrations_platform_connector.apis.TaxGroups.sync", return_value=[] - ) - - mocker.patch( - "xerosdk.apis.TaxRates.get_all", return_value=xero_data["get_all_tax_codes"] - ) - - tax_groups = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type="TAX_CODE" - ).count() - mappings = Mapping.objects.filter( - workspace_id=workspace_id, destination_type="TAX_CODE" - ).count() - - assert tax_groups == 8 - assert mappings == 8 - - existing_tax_codes_name = ExpenseAttribute.objects.filter( - attribute_type="TAX_GROUP", workspace_id=workspace_id - ).first() - existing_tax_codes_name.value = "wertyuio" - existing_tax_codes_name.save() - - auto_create_tax_codes_mappings(workspace_id=workspace_id) - - tax_groups = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type="TAX_CODE" - ).count() - mappings = Mapping.objects.filter( - workspace_id=workspace_id, destination_type="TAX_CODE" - ).count() - - assert mappings == 8 - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - fyle_credentials.delete() - - response = auto_create_tax_codes_mappings(workspace_id) - assert response == None - - with mock.patch( - "fyle_integrations_platform_connector.apis.TaxGroups.sync" - ) as mock_call: - mock_call.side_effect = WrongParamsError( - msg="wrong parameter error", response="wrong parameter error" - ) - response = auto_create_tax_codes_mappings(workspace_id=workspace_id) - - mock_call.side_effect = FyleInvalidTokenError( - msg="Invalid Token for Fyle", response="Invalid Token for Fyle" - ) - response = auto_create_tax_codes_mappings(workspace_id=workspace_id) - - mock_call.side_effect = InternalServerError( - msg="Internal server error while importing to Fyle", - response="Internal server error while importing to Fyle", - ) - response = auto_create_tax_codes_mappings(workspace_id=workspace_id) - - -def test_schedule_tax_groups_creation(db): - workspace_id = 1 - schedule_tax_groups_creation(import_tax_codes=True, workspace_id=workspace_id) - - schedule = Schedule.objects.filter( - func="apps.mappings.tasks.auto_create_tax_codes_mappings", - args="{}".format(workspace_id), - ).first() - - assert schedule.func == "apps.mappings.tasks.auto_create_tax_codes_mappings" - - schedule_tax_groups_creation(import_tax_codes=False, workspace_id=workspace_id) - - schedule = Schedule.objects.filter( - func="apps.mappings.tasks.auto_create_tax_codes_mappings", - args="{}".format(workspace_id), - ).first() - - assert schedule == None - - -def test_auto_create_project_mappings(db, mocker): - workspace_id = 1 - mocker.patch( - "fyle_integrations_platform_connector.apis.Projects.post_bulk", return_value=[] - ) - mocker.patch( - "fyle_integrations_platform_connector.apis.Projects.sync", return_value=[] - ) - mocker.patch( - "xerosdk.apis.Contacts.list_all_generator", - return_value=xero_data["get_all_contacts"], - ) - mocker.patch("apps.xero.utils.XeroConnector.sync_customers", return_value=[]) - existing_project_names = ExpenseAttribute.objects.filter( - attribute_type="PROJECT", workspace_id=workspace_id - ).first() - existing_project_names.value = "asdfghj" - existing_project_names.save() - - response = auto_create_project_mappings(workspace_id=workspace_id) - assert response == None - - projects = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type="PROJECT" - ).count() - mappings = Mapping.objects.filter( - workspace_id=workspace_id, destination_type="PROJECT" - ).count() - assert mappings == projects - - with mock.patch("apps.xero.utils.XeroConnector.sync_customers") as mock_call: - mock_call.side_effect = WrongParamsError( - msg="wrong parameter error", response="wrong parameter error" - ) - response = auto_create_project_mappings(workspace_id=workspace_id) - - mock_call.side_effect = FyleInvalidTokenError( - msg="Invalid Token for Fyle", response="Invalid Token for Fyle" - ) - response = auto_create_project_mappings(workspace_id=workspace_id) - - mock_call.side_effect = InternalServerError( - msg="Internal server error while importing to Fyle", - response="Internal server error while importing to Fyle", - ) - response = auto_create_project_mappings(workspace_id=workspace_id) - - mock_call.side_effect = Exception() - response = auto_create_project_mappings(workspace_id=workspace_id) - - -def test_remove_duplicates(db): - attributes = DestinationAttribute.objects.filter(attribute_type="EMPLOYEE") - assert len(attributes) == 0 - - attributes = remove_duplicates(attributes) - assert len(attributes) == 0 - - -def test_upload_categories_to_fyle(db, mocker): - mocker.patch( - "fyle_integrations_platform_connector.apis.Categories.post_bulk", - return_value=[], - ) - mocker.patch( - "fyle_integrations_platform_connector.apis.Categories.sync", return_value=[] - ) - mocker.patch( - "fyle.platform.apis.v1beta.admin.Categories.list_all", - return_value=fyle_data["get_all_categories"], - ) - mocker.patch("apps.xero.utils.XeroConnector.sync_accounts", return_value=[]) - - workspace_id = 1 - - da_instance = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type="ACCOUNT" - ).first() - da_instance.active = False - da_instance.save() - - ea_instance = ExpenseAttribute.objects.filter(attribute_type="CATEGORY").first() - ea_instance.active = True - - Mapping.objects.create( - destination_type="ACCOUNT", - destination=da_instance, - workspace_id=workspace_id, - source=ea_instance, - ) - - ea_instance.save() - - xero_attributes = upload_categories_to_fyle(workspace_id=workspace_id) - assert len(xero_attributes) == 29 - - xero_credentials = XeroCredentials.objects.get(workspace_id=workspace_id) - xero_credentials.delete() - - # Expect XeroCredentials.DoesNotExist exception since we've deleted the credentials - with pytest.raises(XeroCredentials.DoesNotExist): - xero_attributes = upload_categories_to_fyle(workspace_id=workspace_id) - - -def test_create_fyle_category_payload(mocker, db): - workspace_id = 1 - qbo_attributes = DestinationAttribute.objects.filter( - workspace_id=1, attribute_type="ACCOUNT" - ) - - mocker.patch( - "fyle.platform.apis.v1beta.admin.Categories.list_all", - return_value=fyle_data["get_all_categories"], - ) - - qbo_attributes = remove_duplicates(qbo_attributes) - - fyle_credentials: FyleCredential = FyleCredential.objects.get( - workspace_id=workspace_id - ) - platform = PlatformConnector(fyle_credentials) - - category_map = get_all_categories_from_fyle(platform=platform) - fyle_category_payload = create_fyle_categories_payload( - qbo_attributes, 2, category_map - ) - - assert ( - dict_compare_keys(fyle_category_payload[0], data["fyle_category_payload"][0]) - == [] - ), "category upload api return diffs in keys" - - -def test_auto_create_category_mappings(db, mocker): - workspace_id = 1 - mocker.patch( - "fyle_integrations_platform_connector.apis.Categories.post_bulk", - return_value=[], - ) - - mocker.patch( - "xerosdk.apis.Accounts.get_all", return_value=xero_data["get_all_accounts"] - ) - - mocker.patch( - "fyle.platform.apis.v1beta.admin.Categories.list_all", - return_value=fyle_data["get_all_categories"], - ) - - response = auto_create_category_mappings(workspace_id=workspace_id) - assert response == [] - - mappings = CategoryMapping.objects.filter(workspace_id=workspace_id) - - assert len(mappings) == 0 - - with mock.patch("apps.mappings.tasks.upload_categories_to_fyle") as mock_call: - mock_call.side_effect = WrongParamsError( - msg="wrong parameter error", response="wrong parameter error" - ) - response = auto_create_category_mappings(workspace_id=workspace_id) - - mock_call.side_effect = FyleInvalidTokenError( - msg="Invalid Token for Fyle", response="Invalid Token for Fyle" - ) - response = auto_create_category_mappings(workspace_id=workspace_id) - - mock_call.side_effect = InternalServerError( - msg="Internal server error while importing to Fyle", - response="Internal server error while importing to Fyle", - ) - response = auto_create_category_mappings(workspace_id=workspace_id) - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - fyle_credentials.delete() - - response = auto_create_category_mappings(workspace_id=workspace_id) - - assert response == None - - def test_async_auto_map_employees(mocker, db): workspace_id = 1 @@ -395,183 +105,6 @@ def test_schedule_auto_map_employees(db): assert schedule == None -def test_auto_create_cost_center_mappings(db, mocker, create_mapping_setting): - workspace_id = 1 - mocker.patch( - "fyle_integrations_platform_connector.apis.CostCenters.post_bulk", - return_value=[], - ) - - mocker.patch( - "fyle_integrations_platform_connector.apis.CostCenters.sync", return_value=[] - ) - - mocker.patch( - "xerosdk.apis.TrackingCategories.get_all", - return_value=xero_data["get_all_tracking_categories"], - ) - - response = auto_create_cost_center_mappings(workspace_id=workspace_id) - assert response == None - - cost_center = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type="COST_CENTER" - ).count() - mappings = Mapping.objects.filter( - workspace_id=workspace_id, source_type="COST_CENTER" - ).count() - - assert cost_center == 1 - assert mappings == 0 - - with mock.patch( - "fyle_integrations_platform_connector.apis.CostCenters.sync" - ) as mock_call: - mock_call.side_effect = WrongParamsError( - msg="wrong parameter error", response="wrong parameter error" - ) - response = auto_create_cost_center_mappings(workspace_id=workspace_id) - - mock_call.side_effect = FyleInvalidTokenError( - msg="Invalid Token for Fyle", response="Inalid Token for Fyle" - ) - response = auto_create_cost_center_mappings(workspace_id=workspace_id) - - mock_call.side_effect = InternalServerError( - msg="Internal server error while importing to Fyle", - response="Internal server error while importing to Fyle", - ) - response = auto_create_cost_center_mappings(workspace_id=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 = 1 - - 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): - workspace_id = 1 - - mapping_setting = MappingSetting.objects.last() - mapping_setting.is_custom = True - mapping_setting.import_to_fyle = True - mapping_setting.save() - - schedule_fyle_attributes_creation(workspace_id) - - mocker.patch( - "fyle_integrations_platform_connector.apis.ExpenseCustomFields.post", - return_value=[], - ) - - mocker.patch( - "xerosdk.apis.TrackingCategories.get_all", - return_value=xero_data["get_all_tracking_categories"], - ) - - 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 - ).all() - 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), - ).count() - - assert schedule == 0 - - -def test_auto_create_expense_fields_mappings(db, mocker, create_mapping_setting): - mocker.patch( - "fyle_integrations_platform_connector.apis.ExpenseCustomFields.post", - return_value=[], - ) - mocker.patch( - "fyle_integrations_platform_connector.apis.ExpenseCustomFields.sync", - return_value=[], - ) - 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", - "is_mandatory": True, - }, - ) - workspace_id = 1 - - auto_create_expense_fields_mappings(workspace_id, "COST_CENTER", "TESTING_THIS") - - cost_center = DestinationAttribute.objects.filter( - workspace_id=workspace_id, attribute_type="COST_CENTER" - ).count() - mappings = Mapping.objects.filter( - workspace_id=workspace_id, source_type="COST_CENTER" - ).count() - - assert cost_center == 1 - assert mappings == 0 - - with mock.patch( - "fyle_integrations_platform_connector.apis.ExpenseCustomFields.post" - ) as mock_call: - mock_call.side_effect = WrongParamsError( - msg="wrong parameter error", response="wrong parameter error" - ) - auto_create_expense_fields_mappings(workspace_id, "COST_CENTER", "TESTING_THIS") - - mock_call.side_effect = FyleInvalidTokenError( - msg="Invalid Token for Fyle", response="Invalid Token for Fyle" - ) - auto_create_expense_fields_mappings(workspace_id, "COST_CENTER", "TESTING_THIS") - - mock_call.side_effect = InternalServerError( - msg="Internal server error while importing to Fyle", - response="Internal server error while importing to Fyle", - ) - auto_create_expense_fields_mappings(workspace_id, "COST_CENTER", "TESTING_THIS") - - mock_call.side_effect = Exception() - auto_create_expense_fields_mappings(workspace_id, "COST_CENTER", "TESTING_THIS") - - def test_resolve_expense_attribute_errors(db): workspace_id = 1 expense_group = ExpenseGroup.objects.get(id=3) @@ -602,16 +135,3 @@ def test_resolve_expense_attribute_errors(db): resolve_expense_attribute_errors("EMPLOYEE", workspace_id, "CONTACT") assert Error.objects.get(id=error.id).is_resolved == True - - -def test_auto_import_and_map_fyle_fields(db): - workspace_id = 1 - - auto_import_and_map_fyle_fields(workspace_id=workspace_id) - - schedule = Schedule.objects.filter( - func="apps.mappings.tasks.auto_import_and_map_fyle_fields", - args="{}".format(workspace_id), - ).first() - - assert schedule == None diff --git a/tests/test_xero/test_views.py b/tests/test_xero/test_views.py index 1986536a..69833936 100644 --- a/tests/test_xero/test_views.py +++ b/tests/test_xero/test_views.py @@ -118,7 +118,9 @@ def test_post_sync_dimensions(mocker, api_client, test_connection): def test_post_refresh_dimensions(mocker, api_client, test_connection): mocker.patch("apps.xero.utils.XeroConnector.sync_dimensions", return_value=None) - mocker.patch("apps.mappings.signals.upload_attributes_to_fyle", return_value=[]) + expense_field_mock = mocker.patch("apps.mappings.signals.ExpenseCustomField") + expense_field_mock.return_value.sync_expense_attributes.return_value = [] + workspace_id = 1 MappingSetting.objects.update_or_create(