diff --git a/.flake8 b/.flake8 index c7bff1ca..b17bdab5 100644 --- a/.flake8 +++ b/.flake8 @@ -28,3 +28,4 @@ max-line-length = 99 max-complexity = 19 ban-relative-imports = true select = B,C,E,F,N,W,I25 +exclude=*env diff --git a/.github/workflows/production_deployment.yml b/.github/workflows/production_deployment.yml index 859fa253..3f1e8bb3 100644 --- a/.github/workflows/production_deployment.yml +++ b/.github/workflows/production_deployment.yml @@ -80,4 +80,4 @@ jobs: sentry-cli releases finalize $SENTRY_RELEASE # Create new deploy for this Sentry release - sentry-cli releases deploys $SENTRY_RELEASE new -e $SENTRY_DEPLOY_ENVIRONMENT \ No newline at end of file + sentry-cli releases deploys $SENTRY_RELEASE new -e $SENTRY_DEPLOY_ENVIRONMENT diff --git a/.gitignore b/.gitignore index 2fe2847c..c3683079 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +fyle_integrations_platform_connector/ # PyInstaller # Usually these files are written by a python script from a template diff --git a/apps/fyle/actions.py b/apps/fyle/actions.py index 1cd98aae..0d6f30e3 100644 --- a/apps/fyle/actions.py +++ b/apps/fyle/actions.py @@ -1,12 +1,18 @@ from datetime import datetime, timezone +from typing import List +from django.conf import settings from django.db.models import Q + from fyle_integrations_platform_connector import PlatformConnector +from fyle_accounting_mappings.models import ExpenseAttribute from apps.fyle.constants import DEFAULT_FYLE_CONDITIONS -from apps.fyle.models import ExpenseAttribute, ExpenseGroup +from apps.fyle.models import ExpenseGroup, Expense from apps.workspaces.models import FyleCredential, Workspace, WorkspaceGeneralSettings +from .helpers import get_updated_accounting_export_summary + def get_expense_group_ids(workspace_id: int): configuration = WorkspaceGeneralSettings.objects.get(workspace_id=workspace_id) @@ -74,3 +80,147 @@ def get_custom_fields(workspace_id: int): if custom_field['type'] in ('SELECT', 'NUMBER', 'TEXT'): response.append({'field_name': custom_field['field_name'], 'type': custom_field['type'], 'is_custom': custom_field['is_custom']}) return response + + +def __bulk_update_expenses(expense_to_be_updated: List[Expense]) -> None: + """ + Bulk update expenses + :param expense_to_be_updated: expenses to be updated + :return: None + """ + if expense_to_be_updated: + Expense.objects.bulk_update(expense_to_be_updated, ['is_skipped', 'accounting_export_summary'], batch_size=50) + + +def update_expenses_in_progress(in_progress_expenses: List[Expense]) -> None: + """ + Update expenses in progress in bulk + :param in_progress_expenses: in progress expenses + :return: None + """ + expense_to_be_updated = [] + for expense in in_progress_expenses: + expense_to_be_updated.append( + Expense( + id=expense.id, + accounting_export_summary=get_updated_accounting_export_summary( + expense.expense_id, + 'IN_PROGRESS', + None, + '{}/workspaces/main/dashboard'.format(settings.QBO_INTEGRATION_APP_URL), + False + ) + ) + ) + + __bulk_update_expenses(expense_to_be_updated) + + +def mark_expenses_as_skipped(final_query: Q, expenses_object_ids: List, workspace: Workspace) -> None: + """ + Mark expenses as skipped in bulk + :param final_query: final query + :param expenses_object_ids: expenses object ids + :param workspace: workspace object + :return: None + """ + # We'll iterate through the list of expenses to be skipped, construct accounting export summary and update expenses + expense_to_be_updated = [] + expenses_to_be_skipped = Expense.objects.filter( + final_query, + id__in=expenses_object_ids, + expensegroup__isnull=True, + org_id=workspace.fyle_org_id + ) + + for expense in expenses_to_be_skipped: + expense_to_be_updated.append( + Expense( + id=expense.id, + is_skipped=True, + accounting_export_summary=get_updated_accounting_export_summary( + expense.expense_id, + 'SKIPPED', + None, + '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + False + ) + ) + ) + + __bulk_update_expenses(expense_to_be_updated) + + +def mark_accounting_export_summary_as_synced(expenses: List[Expense]) -> None: + """ + Mark accounting export summary as synced in bulk + :param expenses: List of expenses + :return: None + """ + # Mark all expenses as synced + expense_to_be_updated = [] + for expense in expenses: + expense.accounting_export_summary['synced'] = True + updated_accounting_export_summary = expense.accounting_export_summary + expense_to_be_updated.append( + Expense( + id=expense.id, + accounting_export_summary=updated_accounting_export_summary, + previous_export_state=updated_accounting_export_summary['state'] + ) + ) + + Expense.objects.bulk_update(expense_to_be_updated, ['accounting_export_summary', 'previous_export_state'], batch_size=50) + + +def update_failed_expenses(failed_expenses: List[Expense], is_mapping_error: bool) -> None: + """ + Update failed expenses + :param failed_expenses: Failed expenses + """ + expense_to_be_updated = [] + for expense in failed_expenses: + error_type = 'MAPPING' if is_mapping_error else 'ACCOUNTING_INTEGRATION_ERROR' + + # Skip dummy updates (if it is already in error state with the same error type) + if not (expense.accounting_export_summary.get('state') == 'ERROR' and \ + expense.accounting_export_summary.get('error_type') == error_type): + expense_to_be_updated.append( + Expense( + id=expense.id, + accounting_export_summary=get_updated_accounting_export_summary( + expense.expense_id, + 'ERROR', + error_type, + '{}/workspaces/main/dashboard'.format(settings.QBO_INTEGRATION_APP_URL), + False + ) + ) + ) + + __bulk_update_expenses(expense_to_be_updated) + + +def update_complete_expenses(exported_expenses: List[Expense], url: str) -> None: + """ + Update complete expenses + :param exported_expenses: Exported expenses + :param url: Export url + :return: None + """ + expense_to_be_updated = [] + for expense in exported_expenses: + expense_to_be_updated.append( + Expense( + id=expense.id, + accounting_export_summary=get_updated_accounting_export_summary( + expense.expense_id, + 'COMPLETE', + None, + url, + False + ) + ) + ) + + __bulk_update_expenses(expense_to_be_updated) diff --git a/apps/fyle/helpers.py b/apps/fyle/helpers.py index e4d3afe6..0269088b 100644 --- a/apps/fyle/helpers.py +++ b/apps/fyle/helpers.py @@ -1,5 +1,5 @@ import json -from typing import List +from typing import List, Union import requests from django.conf import settings @@ -22,7 +22,7 @@ def post_request(url, body, refresh_token=None): response = requests.post(url, headers=api_headers, data=body) - if response.status_code == 200: + if response.status_code in [200, 201]: return json.loads(response.text) else: raise Exception(response.text) @@ -150,3 +150,23 @@ def construct_expense_filter(expense_filter): # Return the constructed expense filter return constructed_expense_filter + + +def get_updated_accounting_export_summary( + expense_id: str, state: str, error_type: Union[str, None], url: Union[str, None], is_synced: bool) -> dict: + """ + Get updated accounting export summary + :param expense_id: expense id + :param state: state + :param error_type: error type + :param url: url + :param is_synced: is synced + :return: updated accounting export summary + """ + return { + 'id': expense_id, + 'state': state, + 'error_type': error_type, + 'url': url, + 'synced': is_synced + } diff --git a/apps/fyle/migrations/0032_expense_accounting_export_summary.py b/apps/fyle/migrations/0032_expense_accounting_export_summary.py new file mode 100644 index 00000000..ebc01d88 --- /dev/null +++ b/apps/fyle/migrations/0032_expense_accounting_export_summary.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2023-09-01 13:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fyle', '0031_auto_20230801_1215'), + ] + + operations = [ + migrations.AddField( + model_name='expense', + name='accounting_export_summary', + field=models.JSONField(default=dict), + ), + ] diff --git a/apps/fyle/migrations/0033_expense_workspace.py b/apps/fyle/migrations/0033_expense_workspace.py new file mode 100644 index 00000000..a5c42540 --- /dev/null +++ b/apps/fyle/migrations/0033_expense_workspace.py @@ -0,0 +1,20 @@ +# Generated by Django 3.1.14 on 2023-09-05 12:17 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('workspaces', '0042_workspacegeneralsettings_name_in_journal_entry'), + ('fyle', '0032_expense_accounting_export_summary'), + ] + + operations = [ + migrations.AddField( + model_name='expense', + name='workspace', + field=models.ForeignKey(help_text='To which workspace this expense belongs to', null=True, on_delete=django.db.models.deletion.PROTECT, to='workspaces.workspace'), + ), + ] diff --git a/apps/fyle/migrations/0034_expense_previous_export_state.py b/apps/fyle/migrations/0034_expense_previous_export_state.py new file mode 100644 index 00000000..7c156dfa --- /dev/null +++ b/apps/fyle/migrations/0034_expense_previous_export_state.py @@ -0,0 +1,18 @@ +# Generated by Django 3.1.14 on 2023-09-07 10:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fyle', '0033_expense_workspace'), + ] + + operations = [ + migrations.AddField( + model_name='expense', + name='previous_export_state', + field=models.CharField(help_text='Previous export state', max_length=255, null=True), + ), + ] diff --git a/apps/fyle/models.py b/apps/fyle/models.py index 343186ab..3f5861ca 100644 --- a/apps/fyle/models.py +++ b/apps/fyle/models.py @@ -1,6 +1,7 @@ """ Fyle Models """ +from collections import defaultdict from datetime import datetime from typing import Dict, List @@ -99,9 +100,14 @@ class Expense(models.Model): fund_source = models.CharField(max_length=255, help_text='Expense fund source') verified_at = models.DateTimeField(help_text='Report verified at', null=True) custom_properties = JSONField(null=True) + previous_export_state = models.CharField(max_length=255, help_text='Previous export state', null=True) + accounting_export_summary = JSONField(default=dict) paid_on_qbo = models.BooleanField(help_text='Expense Payment status on QBO', default=False) payment_number = models.CharField(max_length=55, help_text='Expense payment number', null=True) is_skipped = models.BooleanField(null=True, default=False, help_text='Expense is skipped or not') + workspace = models.ForeignKey( + Workspace, on_delete=models.PROTECT, help_text='To which workspace this expense belongs to', null=True + ) class Meta: db_table = 'expenses' @@ -154,6 +160,7 @@ def create_expense_objects(expenses: List[Dict], workspace_id: int): 'verified_at': expense['verified_at'], 'custom_properties': expense['custom_properties'], 'payment_number': expense['payment_number'], + 'workspace_id': workspace_id }, ) @@ -324,12 +331,26 @@ def create_expense_groups_by_report_id_fund_source(expense_objects: List[Expense if general_settings.reimbursable_expenses_object == 'EXPENSE' and 'expense_id' not in reimbursable_expense_group_fields: total_amount = 0 - for expense in reimbursable_expenses: - total_amount += expense.amount - - if total_amount < 0: - reimbursable_expenses = list(filter(lambda expense: expense.amount > 0, reimbursable_expenses)) + if 'spent_at' in reimbursable_expense_group_fields: + grouped_data = defaultdict(list) + for expense in reimbursable_expenses: + spent_at = expense.spent_at + grouped_data[spent_at].append(expense) + grouped_expenses = list(grouped_data.values()) + reimbursable_expenses = [] + for expense_group in grouped_expenses: + total_amount = 0 + for expense in expense_group: + total_amount += expense.amount + if total_amount < 0: + expense_group = list(filter(lambda expense: expense.amount > 0, expense_group)) + reimbursable_expenses.extend(expense_group) + else: + for expense in reimbursable_expenses: + total_amount += expense.amount + if total_amount < 0: + reimbursable_expenses = list(filter(lambda expense: expense.amount > 0, reimbursable_expenses)) elif general_settings.reimbursable_expenses_object != 'JOURNAL ENTRY': reimbursable_expenses = list(filter(lambda expense: expense.amount > 0, reimbursable_expenses)) diff --git a/apps/fyle/queue.py b/apps/fyle/queue.py new file mode 100644 index 00000000..0125dd95 --- /dev/null +++ b/apps/fyle/queue.py @@ -0,0 +1,12 @@ +from django_q.tasks import async_task + + +def async_post_accounting_export_summary(org_id: str, workspace_id: int) -> None: + """ + Async'ly post accounting export summary to Fyle + :param org_id: org id + :param workspace_id: workspace id + :return: None + """ + # This function calls post_accounting_export_summary asynchrously + async_task('apps.fyle.tasks.post_accounting_export_summary', org_id, workspace_id) diff --git a/apps/fyle/tasks.py b/apps/fyle/tasks.py index 7702c7d2..fb156fbe 100644 --- a/apps/fyle/tasks.py +++ b/apps/fyle/tasks.py @@ -4,7 +4,7 @@ from typing import List from django.db import transaction -from fyle.platform.exceptions import InvalidTokenError as FyleInvalidTokenError +from fyle.platform.exceptions import InvalidTokenError as FyleInvalidTokenError, InternalServerError from fyle_integrations_platform_connector import PlatformConnector from apps.fyle.helpers import construct_expense_filter_query @@ -12,6 +12,9 @@ from apps.tasks.models import TaskLog from apps.workspaces.models import FyleCredential, Workspace, WorkspaceGeneralSettings +from .actions import mark_expenses_as_skipped, mark_accounting_export_summary_as_synced +from .queue import async_post_accounting_export_summary + logger = logging.getLogger(__name__) logger.level = logging.INFO @@ -126,7 +129,8 @@ def async_create_expense_groups(workspace_id: int, fund_source: List[str], task_ if expense_filters: expenses_object_ids = [expense_object.id for expense_object in expense_objects] final_query = construct_expense_filter_query(expense_filters) - Expense.objects.filter(final_query, id__in=expenses_object_ids, expensegroup__isnull=True, org_id=workspace.fyle_org_id).update(is_skipped=True) + mark_expenses_as_skipped(final_query, expenses_object_ids, workspace) + async_post_accounting_export_summary(workspace.fyle_org_id, workspace_id) filtered_expenses = Expense.objects.filter(is_skipped=False, id__in=expenses_object_ids, expensegroup__isnull=True, org_id=workspace.fyle_org_id) @@ -156,3 +160,42 @@ def sync_dimensions(fyle_credentials): platform.import_fyle_dimensions(import_taxes=True) except FyleInvalidTokenError: logger.info('Invalid Token for fyle') + + +def post_accounting_export_summary(org_id: str, workspace_id: int) -> None: + """ + Post accounting export summary to Fyle + :param org_id: org id + :param workspace_id: workspace id + :return: None + """ + # Iterate through all expenses which are not synced and post accounting export summary to Fyle in batches + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + platform = PlatformConnector(fyle_credentials) + expenses_count = Expense.objects.filter( + org_id=org_id, accounting_export_summary__synced=False + ).count() + + page_size = 200 + for offset in range(0, expenses_count, page_size): + limit = offset + page_size + paginated_expenses = Expense.objects.filter( + org_id=org_id, accounting_export_summary__synced=False + )[offset:limit] + + payload = [] + + for expense in paginated_expenses: + accounting_export_summary = expense.accounting_export_summary + accounting_export_summary.pop('synced') + payload.append(expense.accounting_export_summary) + + if payload: + try: + platform.expenses.post_bulk_accounting_export_summary(payload) + mark_accounting_export_summary_as_synced(paginated_expenses) + except InternalServerError: + logger.error( + 'Internal server error while posting accounting export summary to Fyle workspace_id: %s', + workspace_id + ) diff --git a/apps/mappings/tasks.py b/apps/mappings/tasks.py index 62387fc8..59d31f07 100644 --- a/apps/mappings/tasks.py +++ b/apps/mappings/tasks.py @@ -766,7 +766,7 @@ def post_merchants(platform_connection: PlatformConnector, workspace_id: int): if fyle_payload: platform_connection.merchants.post(fyle_payload) - platform_connection.merchants.sync(workspace_id) + platform_connection.merchants.sync() @handle_import_exceptions(task_name='Auto Create Vendors as Merchants') @@ -775,7 +775,7 @@ def auto_create_vendors_as_merchants(workspace_id): fyle_connection = PlatformConnector(fyle_credentials) - fyle_connection.merchants.sync(workspace_id) + fyle_connection.merchants.sync() sync_qbo_attribute('VENDOR', workspace_id) post_merchants(fyle_connection, workspace_id) diff --git a/apps/quickbooks_online/actions.py b/apps/quickbooks_online/actions.py index 7c9ecf34..af6aad51 100644 --- a/apps/quickbooks_online/actions.py +++ b/apps/quickbooks_online/actions.py @@ -1,16 +1,28 @@ from datetime import datetime, timezone +import logging +from django.conf import settings from django.db.models import Q + from django_q.tasks import Chain + from fyle_accounting_mappings.models import MappingSetting from qbosdk.exceptions import InvalidTokenError, WrongParamsError from rest_framework.response import Response from rest_framework.views import status +from apps.fyle.models import ExpenseGroup +from apps.fyle.actions import update_complete_expenses from apps.quickbooks_online.utils import QBOConnector from apps.tasks.models import TaskLog from apps.workspaces.models import LastExportDetail, QBOCredential, Workspace +from .helpers import generate_export_type_and_id + + +logger = logging.getLogger(__name__) +logger.level = logging.INFO + def update_last_export_details(workspace_id): last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id) @@ -83,3 +95,24 @@ def sync_quickbooks_dimensions(workspace_id: int): workspace.destination_synced_at = datetime.now() workspace.save(update_fields=['destination_synced_at']) + + +def generate_export_url_and_update_expense(expense_group: ExpenseGroup) -> None: + """ + Generate export url and update expense + :param expense_group: Expense Group + :return: None + """ + try: + export_type, export_id = generate_export_type_and_id(expense_group) + url = '{qbo_app_url}/app/{export_type}?txnId={export_id}'.format( + qbo_app_url=settings.QBO_APP_URL, + export_type=export_type, + export_id=export_id + ) + except Exception as error: + # Defaulting it to QBO app url, worst case scenario if we're not able to parse it properly + url = settings.QBO_APP_URL + logger.error('Error while generating export url %s', error) + + update_complete_expenses(expense_group.expenses.all(), url) diff --git a/apps/quickbooks_online/exceptions.py b/apps/quickbooks_online/exceptions.py index c2cd6a18..41513203 100644 --- a/apps/quickbooks_online/exceptions.py +++ b/apps/quickbooks_online/exceptions.py @@ -5,6 +5,7 @@ from qbosdk.exceptions import InvalidTokenError, WrongParamsError from apps.fyle.models import ExpenseGroup +from apps.fyle.actions import update_failed_expenses from apps.quickbooks_online.actions import update_last_export_details from apps.tasks.models import Error, TaskLog from apps.workspaces.models import FyleCredential, QBOCredential @@ -66,8 +67,12 @@ def new_fn(*args): logger.info('Fyle credentials not found %s', expense_group.workspace_id) task_log.detail = {'message': 'Fyle credentials do not exist in workspace'} task_log.status = 'FAILED' + task_log.save() + if not bill_payment: + update_failed_expenses(expense_group.expenses.all(), True) + except QBOCredential.DoesNotExist: logger.info('QBO Account not connected / token expired for workspace_id %s / expense group %s', expense_group.workspace_id, expense_group.id) detail = {'expense_group_id': expense_group.id, 'message': 'QBO Account not connected / token expired'} @@ -76,23 +81,37 @@ def new_fn(*args): task_log.save() + if not bill_payment: + update_failed_expenses(expense_group.expenses.all(), True) + except WrongParamsError as exception: handle_quickbooks_error(exception, expense_group, task_log, 'Bill') + if not bill_payment: + update_failed_expenses(expense_group.expenses.all(), False) + except BulkError as exception: logger.info(exception.response) detail = exception.response task_log.status = 'FAILED' task_log.detail = detail + task_log.save() + if not bill_payment: + update_failed_expenses(expense_group.expenses.all(), True) + except Exception as error: error = traceback.format_exc() task_log.detail = {'error': error} task_log.status = 'FATAL' + task_log.save() logger.error('Something unexpected happened workspace_id: %s %s', task_log.workspace_id, task_log.detail) + if not bill_payment: + update_failed_expenses(expense_group.expenses.all(), True) + if len(args) > 2 and args[2] == True and not bill_payment: update_last_export_details(expense_group.workspace_id) diff --git a/apps/quickbooks_online/helpers.py b/apps/quickbooks_online/helpers.py new file mode 100644 index 00000000..92506124 --- /dev/null +++ b/apps/quickbooks_online/helpers.py @@ -0,0 +1,39 @@ +from apps.fyle.models import ExpenseGroup + + +def generate_export_type_and_id(expense_group: ExpenseGroup) -> tuple: + """ + Generate export type and id + :param expense_group: Expense group object + """ + export_type = None + export_id = None + + # If Bill exists, export_type is bill + if 'Bill' in expense_group.response_logs and expense_group.response_logs['Bill']: + export_type = 'bill' + export_id = expense_group.response_logs['Bill']['Id'] + # If JournalEntry exists, export_type is journal + elif 'JournalEntry' in expense_group.response_logs and expense_group.response_logs['JournalEntry']: + export_type = 'journal' + export_id = expense_group.response_logs['JournalEntry']['Id'] + # If Purchase exists, export_type can be check / expense + elif 'Purchase' in expense_group.response_logs and expense_group.response_logs['Purchase']: + export_id = expense_group.response_logs['Purchase']['Id'] + # If PaymentType is Check, export_type is check + if expense_group.response_logs['Purchase']['PaymentType'] == 'Check': + export_type = 'check' + else: + # Defaulting to expense initially + export_type = 'expense' + # If PaymentType is CreditCard, export_type is creditcardcredit + if expense_group.fund_source == 'CCC' and \ + expense_group.response_logs['Purchase']['PaymentType'] == 'CreditCard' and \ + expense_group.response_logs['Purchase'].get('Credit'): + export_type = 'creditcardcredit' + # If PaymentType is Cash, export_type is expense + elif expense_group.fund_source == 'CCC' \ + and expense_group.response_logs['Purchase']['PaymentType'] == 'Cash': + export_type = 'expense' + + return export_type, export_id diff --git a/apps/quickbooks_online/queue.py b/apps/quickbooks_online/queue.py index 9bc11840..db65214a 100644 --- a/apps/quickbooks_online/queue.py +++ b/apps/quickbooks_online/queue.py @@ -5,7 +5,7 @@ from django_q.models import Schedule from django_q.tasks import Chain, async_task -from apps.fyle.models import ExpenseGroup +from apps.fyle.models import ExpenseGroup, Expense from apps.tasks.models import TaskLog from apps.workspaces.models import FyleCredential, WorkspaceGeneralSettings @@ -14,7 +14,7 @@ def async_run_post_configration_triggers(workspace_general_settings: WorkspaceGe async_task('apps.quickbooks_online.tasks.async_sync_accounts', int(workspace_general_settings.workspace_id)) -def schedule_bills_creation(workspace_id: int, expense_group_ids: List[str]): +def schedule_bills_creation(workspace_id: int, expense_group_ids: List[str], is_auto_export: bool): """ Schedule bills creation :param expense_group_ids: List of expense group ids @@ -24,10 +24,8 @@ def schedule_bills_creation(workspace_id: int, expense_group_ids: List[str]): if expense_group_ids: expense_groups = ExpenseGroup.objects.filter(Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']), workspace_id=workspace_id, id__in=expense_group_ids, bill__id__isnull=True, exported_at__isnull=True).all() - chain = Chain() - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - chain.append('apps.fyle.tasks.sync_dimensions', fyle_credentials) + chain_tasks = [] + in_progress_expenses = [] for index, expense_group in enumerate(expense_groups): task_log, _ = TaskLog.objects.get_or_create(workspace_id=expense_group.workspace_id, expense_group=expense_group, defaults={'status': 'ENQUEUED', 'type': 'CREATING_BILL'}) @@ -40,13 +38,45 @@ def schedule_bills_creation(workspace_id: int, expense_group_ids: List[str]): if expense_groups.count() == index + 1: last_export = True - chain.append('apps.quickbooks_online.tasks.create_bill', expense_group, task_log.id, last_export) + chain_tasks.append({ + 'target': 'apps.quickbooks_online.tasks.create_bill', + 'expense_group': expense_group, + 'task_log_id': task_log.id, + 'last_export': last_export + }) + + # Don't include expenses with previous export state as ERROR and it's an auto import/export run + if not (is_auto_export and expense_group.expenses.first().previous_export_state == 'ERROR'): + in_progress_expenses.extend(expense_group.expenses.all()) + + if len(chain_tasks) > 0: + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + __create_chain_and_run(fyle_credentials, in_progress_expenses, workspace_id, chain_tasks) + + +def __create_chain_and_run(fyle_credentials: FyleCredential, in_progress_expenses: List[Expense], + workspace_id: int, chain_tasks: List[dict]) -> None: + """ + Create chain and run + :param fyle_credentials: Fyle credentials + :param in_progress_expenses: List of in progress expenses + :param workspace_id: workspace id + :param chain_tasks: List of chain tasks + :return: None + """ + chain = Chain() + + chain.append('apps.quickbooks_online.tasks.update_expense_and_post_summary', in_progress_expenses, workspace_id) + chain.append('apps.fyle.tasks.sync_dimensions', fyle_credentials) + + for task in chain_tasks: + chain.append(task['target'], task['expense_group'], task['task_log_id'], task['last_export']) - if chain.length() > 1: - chain.run() + chain.append('apps.fyle.tasks.post_accounting_export_summary', fyle_credentials.workspace.fyle_org_id, workspace_id) + chain.run() -def schedule_cheques_creation(workspace_id: int, expense_group_ids: List[str]): +def schedule_cheques_creation(workspace_id: int, expense_group_ids: List[str], is_auto_export: bool): """ Schedule cheque creation :param expense_group_ids: List of expense group ids @@ -56,10 +86,8 @@ def schedule_cheques_creation(workspace_id: int, expense_group_ids: List[str]): if expense_group_ids: expense_groups = ExpenseGroup.objects.filter(Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']), workspace_id=workspace_id, id__in=expense_group_ids, cheque__id__isnull=True, exported_at__isnull=True).all() - chain = Chain() - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - chain.append('apps.fyle.tasks.sync_dimensions', fyle_credentials) + chain_tasks = [] + in_progress_expenses = [] for index, expense_group in enumerate(expense_groups): task_log, _ = TaskLog.objects.get_or_create(workspace_id=expense_group.workspace_id, expense_group=expense_group, defaults={'status': 'ENQUEUED', 'type': 'CREATING_CHECK'}) @@ -72,13 +100,23 @@ def schedule_cheques_creation(workspace_id: int, expense_group_ids: List[str]): if expense_groups.count() == index + 1: last_export = True - chain.append('apps.quickbooks_online.tasks.create_cheque', expense_group, task_log.id, last_export) + chain_tasks.append({ + 'target': 'apps.quickbooks_online.tasks.create_cheque', + 'expense_group': expense_group, + 'task_log_id': task_log.id, + 'last_export': last_export + }) - if chain.length() > 1: - chain.run() + # Don't include expenses with previous export state as ERROR and it's an auto import/export run + if not (is_auto_export and expense_group.expenses.first().previous_export_state == 'ERROR'): + in_progress_expenses.extend(expense_group.expenses.all()) + if len(chain_tasks) > 0: + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + __create_chain_and_run(fyle_credentials, in_progress_expenses, workspace_id, chain_tasks) -def schedule_journal_entry_creation(workspace_id: int, expense_group_ids: List[str]): + +def schedule_journal_entry_creation(workspace_id: int, expense_group_ids: List[str], is_auto_export: bool): """ Schedule journal_entry creation :param expense_group_ids: List of expense group ids @@ -90,10 +128,8 @@ def schedule_journal_entry_creation(workspace_id: int, expense_group_ids: List[s Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']), workspace_id=workspace_id, id__in=expense_group_ids, journalentry__id__isnull=True, exported_at__isnull=True ).all() - chain = Chain() - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - chain.append('apps.fyle.tasks.sync_dimensions', fyle_credentials) + chain_tasks = [] + in_progress_expenses = [] for index, expense_group in enumerate(expense_groups): task_log, _ = TaskLog.objects.get_or_create(workspace_id=expense_group.workspace_id, expense_group=expense_group, defaults={'status': 'ENQUEUED', 'type': 'CREATING_JOURNAL_ENTRY'}) @@ -106,13 +142,22 @@ def schedule_journal_entry_creation(workspace_id: int, expense_group_ids: List[s if expense_groups.count() == index + 1: last_export = True - chain.append('apps.quickbooks_online.tasks.create_journal_entry', expense_group, task_log.id, last_export) + chain_tasks.append({ + 'target': 'apps.quickbooks_online.tasks.create_journal_entry', + 'expense_group': expense_group, + 'task_log_id': task_log.id, + 'last_export': last_export + }) + # Don't include expenses with previous export state as ERROR and it's an auto import/export run + if not (is_auto_export and expense_group.expenses.first().previous_export_state == 'ERROR'): + in_progress_expenses.extend(expense_group.expenses.all()) - if chain.length() > 1: - chain.run() + if len(chain_tasks) > 0: + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + __create_chain_and_run(fyle_credentials, in_progress_expenses, workspace_id, chain_tasks) -def schedule_credit_card_purchase_creation(workspace_id: int, expense_group_ids: List[str]): +def schedule_credit_card_purchase_creation(workspace_id: int, expense_group_ids: List[str], is_auto_export: bool): """ Schedule credit card purchase creation :param expense_group_ids: List of expense group ids @@ -124,10 +169,8 @@ def schedule_credit_card_purchase_creation(workspace_id: int, expense_group_ids: Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']), workspace_id=workspace_id, id__in=expense_group_ids, creditcardpurchase__id__isnull=True, exported_at__isnull=True ).all() - chain = Chain() - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - chain.append('apps.fyle.tasks.sync_dimensions', fyle_credentials) + chain_tasks = [] + in_progress_expenses = [] for index, expense_group in enumerate(expense_groups): task_log, _ = TaskLog.objects.get_or_create(workspace_id=expense_group.workspace_id, expense_group=expense_group, defaults={'status': 'ENQUEUED', 'type': 'CREATING_CREDIT_CARD_PURCHASE'}) @@ -140,13 +183,23 @@ def schedule_credit_card_purchase_creation(workspace_id: int, expense_group_ids: if expense_groups.count() == index + 1: last_export = True - chain.append('apps.quickbooks_online.tasks.create_credit_card_purchase', expense_group, task_log.id, last_export) + chain_tasks.append({ + 'target': 'apps.quickbooks_online.tasks.create_credit_card_purchase', + 'expense_group': expense_group, + 'task_log_id': task_log.id, + 'last_export': last_export + }) + + # Don't include expenses with previous export state as ERROR and it's an auto import/export run + if not (is_auto_export and expense_group.expenses.first().previous_export_state == 'ERROR'): + in_progress_expenses.extend(expense_group.expenses.all()) - if chain.length() > 1: - chain.run() + if len(chain_tasks) > 0: + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + __create_chain_and_run(fyle_credentials, in_progress_expenses, workspace_id, chain_tasks) -def schedule_qbo_expense_creation(workspace_id: int, expense_group_ids: List[str]): +def schedule_qbo_expense_creation(workspace_id: int, expense_group_ids: List[str], is_auto_export: bool): """ Schedule QBO expense creation :param expense_group_ids: List of expense group ids @@ -156,10 +209,8 @@ def schedule_qbo_expense_creation(workspace_id: int, expense_group_ids: List[str if expense_group_ids: expense_groups = ExpenseGroup.objects.filter(Q(tasklog__id__isnull=True) | ~Q(tasklog__status__in=['IN_PROGRESS', 'COMPLETE']), workspace_id=workspace_id, id__in=expense_group_ids, qboexpense__id__isnull=True, exported_at__isnull=True).all() - chain = Chain() - - fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) - chain.append('apps.fyle.tasks.sync_dimensions', fyle_credentials) + chain_tasks = [] + in_progress_expenses = [] for index, expense_group in enumerate(expense_groups): task_log, _ = TaskLog.objects.get_or_create( @@ -174,10 +225,20 @@ def schedule_qbo_expense_creation(workspace_id: int, expense_group_ids: List[str if expense_groups.count() == index + 1: last_export = True - chain.append('apps.quickbooks_online.tasks.create_qbo_expense', expense_group, task_log.id, last_export) + chain_tasks.append({ + 'target': 'apps.quickbooks_online.tasks.create_qbo_expense', + 'expense_group': expense_group, + 'task_log_id': task_log.id, + 'last_export': last_export + }) + + # Don't include expenses with previous export state as ERROR and it's an auto import/export run + if not (is_auto_export and expense_group.expenses.first().previous_export_state == 'ERROR'): + in_progress_expenses.extend(expense_group.expenses.all()) - if chain.length() > 1: - chain.run() + if len(chain_tasks) > 0: + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + __create_chain_and_run(fyle_credentials, in_progress_expenses, workspace_id, chain_tasks) def schedule_qbo_objects_status_sync(sync_qbo_to_fyle_payments, workspace_id): diff --git a/apps/quickbooks_online/tasks.py b/apps/quickbooks_online/tasks.py index 3d963384..a5d7d310 100644 --- a/apps/quickbooks_online/tasks.py +++ b/apps/quickbooks_online/tasks.py @@ -1,14 +1,18 @@ import json +from typing import List import logging import traceback from datetime import datetime from django.db import transaction + from fyle_accounting_mappings.models import DestinationAttribute, EmployeeMapping, ExpenseAttribute, Mapping from fyle_integrations_platform_connector import PlatformConnector from qbosdk.exceptions import InvalidTokenError, WrongParamsError from apps.fyle.models import Expense, ExpenseGroup, Reimbursement +from apps.fyle.actions import update_expenses_in_progress +from apps.fyle.tasks import post_accounting_export_summary from apps.mappings.models import GeneralMapping from apps.quickbooks_online.actions import update_last_export_details from apps.quickbooks_online.exceptions import handle_qbo_exceptions @@ -28,9 +32,11 @@ ) from apps.quickbooks_online.utils import QBOConnector from apps.tasks.models import Error, TaskLog -from apps.workspaces.models import FyleCredential, QBOCredential, WorkspaceGeneralSettings +from apps.workspaces.models import FyleCredential, QBOCredential, WorkspaceGeneralSettings, Workspace from fyle_qbo_api.exceptions import BulkError +from .actions import generate_export_url_and_update_expense + logger = logging.getLogger(__name__) logger.level = logging.INFO @@ -209,6 +215,8 @@ def create_bill(expense_group, task_log_id, last_export: bool): load_attachments(qbo_connection, created_bill['Bill']['Id'], 'Bill', expense_group) + generate_export_url_and_update_expense(expense_group) + if last_export: update_last_export_details(expense_group.workspace_id) @@ -369,6 +377,8 @@ def create_cheque(expense_group, task_log_id, last_export: bool): load_attachments(qbo_connection, created_cheque['Purchase']['Id'], 'Purchase', expense_group) + generate_export_url_and_update_expense(expense_group) + if last_export: update_last_export_details(expense_group.workspace_id) @@ -420,6 +430,8 @@ def create_qbo_expense(expense_group, task_log_id, last_export: bool): load_attachments(qbo_connection, created_qbo_expense['Purchase']['Id'], 'Purchase', expense_group) + generate_export_url_and_update_expense(expense_group) + if last_export: update_last_export_details(expense_group.workspace_id) @@ -470,6 +482,8 @@ def create_credit_card_purchase(expense_group: ExpenseGroup, task_log_id, last_e load_attachments(qbo_connection, created_credit_card_purchase['Purchase']['Id'], 'Purchase', expense_group) + generate_export_url_and_update_expense(expense_group) + if last_export: update_last_export_details(expense_group.workspace_id) @@ -518,6 +532,8 @@ def create_journal_entry(expense_group, task_log_id, last_export: bool): load_attachments(qbo_connection, created_journal_entry['JournalEntry']['Id'], 'JournalEntry', expense_group) + generate_export_url_and_update_expense(expense_group) + if last_export: update_last_export_details(expense_group.workspace_id) @@ -659,3 +675,9 @@ def async_sync_accounts(workspace_id): qbo_connection.sync_accounts() except (WrongParamsError, InvalidTokenError) as exception: logger.info('QBO token expired workspace_id - %s %s', workspace_id, {'error': exception.response}) + + +def update_expense_and_post_summary(in_progress_expenses: List[Expense], workspace_id: int) -> None: + fyle_org_id = Workspace.objects.get(pk=workspace_id).fyle_org_id + update_expenses_in_progress(in_progress_expenses) + post_accounting_export_summary(fyle_org_id, workspace_id) diff --git a/apps/workspaces/actions.py b/apps/workspaces/actions.py index a3b8d553..caa8969d 100644 --- a/apps/workspaces/actions.py +++ b/apps/workspaces/actions.py @@ -1,4 +1,5 @@ import logging +import json from datetime import datetime from django.conf import settings @@ -14,7 +15,7 @@ from rest_framework.response import Response from rest_framework.views import status -from apps.fyle.helpers import get_cluster_domain +from apps.fyle.helpers import get_cluster_domain, post_request from apps.fyle.models import ExpenseGroupSettings from apps.quickbooks_online.utils import QBOConnector from apps.workspaces.models import FyleCredential, LastExportDetail, QBOCredential, Workspace @@ -122,6 +123,8 @@ def delete_qbo_refresh_token(workspace_id: int): post_delete_qbo_connection(workspace_id) + post_to_integration_settings(workspace_id, False) + try: revoke_refresh_token(refresh_token, settings.QBO_CLIENT_ID, settings.QBO_CLIENT_SECRET) except Exception as exception: @@ -197,3 +200,23 @@ def setup_e2e_tests(workspace_id: int, connection): error_message = 'No healthy tokens found, please try again later.' logger.error(error) return Response(status=status.HTTP_400_BAD_REQUEST, data={'message': error_message}) + + +def post_to_integration_settings(workspace_id: int, active: bool): + """ + Post to integration settings + """ + refresh_token = FyleCredential.objects.get(workspace_id=workspace_id).refresh_token + url = '{}/integrations/'.format(settings.INTEGRATIONS_SETTINGS_API) + payload = { + 'tpa_id': settings.FYLE_CLIENT_ID, + 'tpa_name': 'Fyle Quickbooks Integration', + 'type': 'ACCOUNTING', + 'is_active': active, + 'connected_at': datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%fZ') + } + + try: + post_request(url, json.dumps(payload), refresh_token) + except Exception as error: + logger.error(error) diff --git a/apps/workspaces/apis/advanced_configurations/serializers.py b/apps/workspaces/apis/advanced_configurations/serializers.py index f4d6bc72..663ca08f 100644 --- a/apps/workspaces/apis/advanced_configurations/serializers.py +++ b/apps/workspaces/apis/advanced_configurations/serializers.py @@ -97,6 +97,7 @@ def update(self, instance, validated): if instance.onboarding_state == 'ADVANCED_CONFIGURATION': instance.onboarding_state = 'COMPLETE' instance.save() + AdvancedConfigurationsTriggers.post_to_integration_settings(instance.id, True) return instance diff --git a/apps/workspaces/apis/advanced_configurations/triggers.py b/apps/workspaces/apis/advanced_configurations/triggers.py index 15c45c90..0b391e53 100644 --- a/apps/workspaces/apis/advanced_configurations/triggers.py +++ b/apps/workspaces/apis/advanced_configurations/triggers.py @@ -1,6 +1,12 @@ +import logging + from apps.mappings.queue import schedule_bill_payment_creation from apps.quickbooks_online.queue import schedule_qbo_objects_status_sync, schedule_reimbursements_sync from apps.workspaces.models import WorkspaceGeneralSettings +from apps.workspaces.actions import post_to_integration_settings + +logger = logging.getLogger(__name__) +logger.level = logging.INFO class AdvancedConfigurationsTriggers: @@ -18,3 +24,10 @@ def run_workspace_general_settings_triggers(workspace_general_settings_instance: schedule_qbo_objects_status_sync(sync_qbo_to_fyle_payments=workspace_general_settings_instance.sync_qbo_to_fyle_payments, workspace_id=workspace_general_settings_instance.workspace.id) schedule_reimbursements_sync(sync_qbo_to_fyle_payments=workspace_general_settings_instance.sync_qbo_to_fyle_payments, workspace_id=workspace_general_settings_instance.workspace.id) + + @staticmethod + def post_to_integration_settings(workspace_id: int, active: bool): + """ + Post to integration settings + """ + post_to_integration_settings(workspace_id, active) diff --git a/apps/workspaces/tasks.py b/apps/workspaces/tasks.py index 370cfeca..35b3b01b 100644 --- a/apps/workspaces/tasks.py +++ b/apps/workspaces/tasks.py @@ -113,6 +113,7 @@ def export_to_qbo(workspace_id, export_mode=None): last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id) last_exported_at = datetime.now() is_expenses_exported = False + export_mode = export_mode or 'MANUAL' if general_settings.reimbursable_expenses_object: @@ -122,16 +123,32 @@ def export_to_qbo(workspace_id, export_mode=None): is_expenses_exported = True if general_settings.reimbursable_expenses_object == 'BILL': - schedule_bills_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_bills_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) elif general_settings.reimbursable_expenses_object == 'EXPENSE': - schedule_qbo_expense_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_qbo_expense_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) elif general_settings.reimbursable_expenses_object == 'CHECK': - schedule_cheques_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_cheques_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) elif general_settings.reimbursable_expenses_object == 'JOURNAL ENTRY': - schedule_journal_entry_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_journal_entry_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) if general_settings.corporate_credit_card_expenses_object: expense_group_ids = ExpenseGroup.objects.filter(fund_source='CCC', exported_at__isnull=True, workspace_id=workspace_id).values_list('id', flat=True) @@ -140,19 +157,35 @@ def export_to_qbo(workspace_id, export_mode=None): is_expenses_exported = True if general_settings.corporate_credit_card_expenses_object == 'JOURNAL ENTRY': - schedule_journal_entry_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_journal_entry_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) elif general_settings.corporate_credit_card_expenses_object == 'CREDIT CARD PURCHASE': - schedule_credit_card_purchase_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_credit_card_purchase_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) elif general_settings.corporate_credit_card_expenses_object == 'DEBIT CARD EXPENSE': - schedule_qbo_expense_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_qbo_expense_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) elif general_settings.corporate_credit_card_expenses_object == 'BILL': - schedule_bills_creation(workspace_id=workspace_id, expense_group_ids=expense_group_ids) + schedule_bills_creation( + workspace_id=workspace_id, + expense_group_ids=expense_group_ids, + is_auto_export=export_mode == 'AUTO' + ) if is_expenses_exported: last_export_detail.last_exported_at = last_exported_at - last_export_detail.export_mode = export_mode or 'MANUAL' + last_export_detail.export_mode = export_mode last_export_detail.save() diff --git a/docker-compose-pipeline.yml b/docker-compose-pipeline.yml index 6d5d3279..e73fde69 100644 --- a/docker-compose-pipeline.yml +++ b/docker-compose-pipeline.yml @@ -34,6 +34,8 @@ services: ENCRYPTION_KEY: ${ENCRYPTION_KEY} E2E_TESTS_CLIENT_SECRET: ${E2E_TESTS_CLIENT_SECRET} E2E_TESTS_REALM_ID: ${E2E_TESTS_REALM_ID} + INTEGRATIONS_SETTINGS_API: http://localhost:8006/api + QBO_APP_URL: https://lolo.qbo.com db: image: "postgres:15" environment: diff --git a/fyle_qbo_api/settings.py b/fyle_qbo_api/settings.py index 5b1ea406..970e9477 100644 --- a/fyle_qbo_api/settings.py +++ b/fyle_qbo_api/settings.py @@ -189,6 +189,9 @@ FYLE_APP_URL = os.environ.get('APP_URL') FYLE_EXPENSE_URL = os.environ.get('FYLE_APP_URL') +QBO_INTEGRATION_APP_URL = os.environ.get('QBO_INTEGRATION_APP_URL') +QBO_APP_URL = os.environ.get('QBO_APP_URL') + # QBO Settings QBO_CLIENT_ID = os.environ.get('QBO_CLIENT_ID') @@ -199,6 +202,7 @@ ENCRYPTION_KEY = os.environ.get('ENCRYPTION_KEY') E2E_TESTS_CLIENT_SECRET = os.environ.get('E2E_TESTS_CLIENT_SECRET') E2E_TESTS_REALM_ID = os.environ.get('E2E_TESTS_REALM_ID') +INTEGRATIONS_SETTINGS_API = os.environ.get('INTEGRATIONS_SETTINGS_API') # Cache Settings SENDGRID_SANDBOX_MODE_IN_DEBUG = False diff --git a/fyle_qbo_api/tests/settings.py b/fyle_qbo_api/tests/settings.py index 3791d53d..80a8607e 100644 --- a/fyle_qbo_api/tests/settings.py +++ b/fyle_qbo_api/tests/settings.py @@ -190,6 +190,9 @@ FYLE_APP_URL = '' EMAIL = '' +QBO_INTEGRATION_APP_URL = os.environ.get('QBO_INTEGRATION_APP_URL') +QBO_APP_URL = os.environ.get('QBO_APP_URL') + # QBO Settings QBO_CLIENT_ID = os.environ.get('QBO_CLIENT_ID') QBO_CLIENT_SECRET = os.environ.get('QBO_CLIENT_SECRET') @@ -199,6 +202,7 @@ ENCRYPTION_KEY = os.environ.get('ENCRYPTION_KEY') E2E_TESTS_CLIENT_SECRET = os.environ.get('E2E_TESTS_CLIENT_SECRET') E2E_TESTS_REALM_ID = os.environ.get('E2E_TESTS_REALM_ID') +INTEGRATIONS_SETTINGS_API = os.environ.get('INTEGRATIONS_SETTINGS_API') # Cache Settings CACHE_EXPIRY = 3600 diff --git a/requirements.txt b/requirements.txt index 3280e194..9f60e62e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,9 +20,9 @@ djangorestframework==3.11.2 django-sendgrid-v5==1.2.0 enum34==1.1.10 future==0.18.2 -fyle==0.30.0 +fyle==0.33.0 fyle-accounting-mappings==1.26.1 -fyle-integrations-platform-connector==1.30.0 +fyle-integrations-platform-connector==1.33.0 fyle-rest-auth==1.5.0 gevent==22.10.2 gunicorn==20.1.0 @@ -50,4 +50,3 @@ pytest-django==4.4.0 pytest-mock==3.6.1 wrapt==1.12.1 gevent==22.10.2 -django-filter==21.1 diff --git a/scripts/python/fill-accounting-export-summary.py b/scripts/python/fill-accounting-export-summary.py new file mode 100644 index 00000000..faa6e754 --- /dev/null +++ b/scripts/python/fill-accounting-export-summary.py @@ -0,0 +1,84 @@ +from datetime import datetime + +from django.db.models import Q +from django.conf import settings + +from apps.tasks.models import TaskLog +from apps.workspaces.models import Workspace +from apps.quickbooks_online.helpers import generate_export_type_and_id +from apps.fyle.helpers import get_updated_accounting_export_summary +from apps.fyle.models import Expense +from apps.fyle.actions import __bulk_update_expenses + +# PLEASE RUN sql/scripts/022-fill-skipped-accounting-export-summary.sql BEFORE RUNNING THIS SCRIPT + + +export_types = ['CREATING_BILL', 'CREATING_EXPENSE', 'CREATING_CHECK', 'CREATING_CREDIT_CARD_PURCHASE', 'CREATING_JOURNAL_ENTRY', 'CREATING_CREDIT_CARD_CREDIT', 'CREATING_DEBIT_CARD_EXPENSE'] +task_statuses = ['COMPLETE', 'FAILED', 'FATAL'] + + +# We'll handle all COMPLETE, ERROR expenses in this script +workspaces = Workspace.objects.filter( + ~Q(name__icontains='fyle for') & ~Q(name__icontains='test') +) + +start_time = datetime.now() +number_of_expenses_without_accounting_export_summary = Expense.objects.filter( + accounting_export_summary__state__isnull=True +).count() +print('Number of expenses without accounting export summary - {}'.format(number_of_expenses_without_accounting_export_summary)) +for workspace in workspaces: + task_logs_count = TaskLog.objects.filter( + type__in=export_types, + workspace_id=workspace.id, + status__in=task_statuses + ).count() + print('Updating summary from workspace - {} with ID - {}'.format(workspace.name, workspace.id)) + print('Number of task logs to be updated - {}'.format(task_logs_count)) + page_size = 200 + for offset in range(0, task_logs_count, page_size): + expense_to_be_updated = [] + limit = offset + page_size + paginated_task_logs = TaskLog.objects.filter( + type__in=export_types, + workspace_id=workspace.id, + status__in=task_statuses + )[offset:limit] + for task_log in paginated_task_logs: + expense_group = task_log.expense_group + state = 'ERROR' if task_log.status == 'FAILED' or task_log.status == 'FATAL' else 'COMPLETE' + error_type = None + url = None + if task_log.status == 'FAILED' or task_log.status == 'FATAL': + error_type = 'ACCOUNTING_INTEGRATION_ERROR' if task_log.quickbooks_errors else 'MAPPING' + url = '{}/workspaces/main/dashboard'.format(settings.QBO_INTEGRATION_APP_URL) + else: + export_type, export_id = generate_export_type_and_id(expense_group) + url = '{qbo_app_url}/app/{export_type}?txnId={export_id}'.format( + qbo_app_url=settings.QBO_APP_URL, + export_type=export_type, + export_id=export_id + ) + for expense in expense_group.expenses.filter(accounting_export_summary__state__isnull=True): + expense_to_be_updated.append( + Expense( + id=expense.id, + accounting_export_summary=get_updated_accounting_export_summary( + expense.expense_id, + state, + error_type, + url, + False + ) + ) + ) + print('Updating {} expenses in batches of 50'.format(len(expense_to_be_updated))) + __bulk_update_expenses(expense_to_be_updated) + + +number_of_expenses_without_accounting_export_summary = Expense.objects.filter( + accounting_export_summary__state__isnull=True +).count() +print('Number of expenses without accounting export summary - {}'.format(number_of_expenses_without_accounting_export_summary)) +end_time = datetime.now() +print('Time taken - {}'.format(end_time - start_time)) diff --git a/scripts/python/post-accounting-export-summary.py b/scripts/python/post-accounting-export-summary.py new file mode 100644 index 00000000..c5be2e33 --- /dev/null +++ b/scripts/python/post-accounting-export-summary.py @@ -0,0 +1,43 @@ +from datetime import datetime +from time import sleep + +from django.db.models import Q + +from apps.workspaces.models import Workspace +from apps.fyle.models import Expense +from apps.fyle.tasks import post_accounting_export_summary + +# PLEASE RUN scripts/python/fill-accounting-export-summary.py BEFORE RUNNING THIS SCRIPT +workspaces = Workspace.objects.filter( + ~Q(name__icontains='fyle for') & ~Q(name__icontains='test') +) + +start_time = datetime.now() +number_of_expenses_to_be_posted = Expense.objects.filter( + accounting_export_summary__synced=False +).count() +print('Number of expenses to be posted - {}'.format(number_of_expenses_to_be_posted)) +for workspace in workspaces: + expenses_count = Expense.objects.filter( + accounting_export_summary__synced=False, + workspace_id=workspace.id + ).count() + print('Updating summary from workspace - {} with ID - {}'.format(workspace.name, workspace.id)) + print('Number of expenses_count to be posted for the current workspace - {}'.format(expenses_count)) + if expenses_count: + try: + sleep(1) + post_accounting_export_summary(workspace.fyle_org_id, workspace.id) + except Exception as e: + print('Error while posting accounting export summary for workspace - {} with ID - {}'.format(workspace.name, workspace.id)) + print(e.__dict__) + +number_of_expenses_posted = Expense.objects.filter( + accounting_export_summary__synced=True +).count() +print('Number of expenses posted to Fyle - {}'.format(number_of_expenses_posted)) +end_time = datetime.now() +print('Time taken - {}'.format(end_time - start_time)) + +# This query should return 0 rows +# select expense_group_id from task_logs where status not in ('ENQUEUED', 'IN_PROGRESS') and expense_group_id in (select expensegroup_id from expense_groups_expenses where expense_id in (select id from expenses where accounting_export_summary ='{}')); diff --git a/sql/functions/delete-workspace-v2.sql b/sql/functions/delete-workspace-v2.sql deleted file mode 100644 index 0023b3a9..00000000 --- a/sql/functions/delete-workspace-v2.sql +++ /dev/null @@ -1,293 +0,0 @@ -DROP FUNCTION if exists delete_workspace_v2; - -CREATE OR REPLACE FUNCTION delete_workspace_v2(IN _workspace_id integer) RETURNS void AS $$ -DECLARE - rcount integer; -BEGIN - RAISE NOTICE 'Deleting data from workspace % ', _workspace_id; - - DELETE - FROM task_logs tl - WHERE tl.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % task_logs', rcount; - - DELETE - FROM errors er - where er.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % errors', rcount; - - DELETE - FROM last_export_details l - where l.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % errors', rcount; - - DELETE - FROM bill_lineitems bl - WHERE bl.bill_id IN ( - SELECT b.id FROM bills b WHERE b.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % bill_lineitems', rcount; - - DELETE - FROM bills b - WHERE b.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % bills', rcount; - - DELETE - FROM qbo_expense_lineitems qel - WHERE qel.qbo_expense_id IN ( - SELECT qe.id FROM qbo_expenses qe WHERE qe.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % qbo_expense_lineitems', rcount; - - DELETE - FROM qbo_expenses qe - WHERE qe.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % qbo_expenses', rcount; - - DELETE - FROM cheque_lineitems cl - WHERE cl.cheque_id IN ( - SELECT c.id FROM cheques c WHERE c.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % cheque_lineitems', rcount; - - DELETE - FROM cheques c - WHERE c.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % cheques', rcount; - - DELETE - FROM credit_card_purchase_lineitems ccpl - WHERE ccpl.credit_card_purchase_id IN ( - SELECT ccp.id FROM credit_card_purchases ccp WHERE ccp.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % credit_card_purchase_lineitems', rcount; - - DELETE - FROM credit_card_purchases ccp - WHERE ccp.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % credit_card_purchases', rcount; - - DELETE - FROM journal_entry_lineitems jel - WHERE jel.journal_entry_id IN ( - SELECT c.id FROM journal_entries c WHERE c.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % journal_entry_lineitems', rcount; - - DELETE - FROM journal_entries je - WHERE je.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % journal_entries', rcount; - - DELETE - FROM bill_payment_lineitems bpl - WHERE bpl.bill_payment_id IN ( - SELECT bp.id FROM bill_payments bp WHERE bp.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % bill_payment_lineitems', rcount; - - DELETE - FROM bill_payments bp - WHERE bp.expense_group_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % bill_payments', rcount; - - DELETE - FROM reimbursements r - WHERE r.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % reimbursements', rcount; - - DELETE - FROM expenses e - WHERE e.id IN ( - SELECT expense_id FROM expense_groups_expenses ege WHERE ege.expensegroup_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expenses', rcount; - - DELETE - FROM expenses - WHERE is_skipped=true and org_id in (SELECT fyle_org_id FROM workspaces WHERE id=_workspace_id); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % skipped expenses', rcount; - - DELETE - FROM expense_groups_expenses ege - WHERE ege.expensegroup_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expense_groups_expenses', rcount; - - DELETE - FROM expense_groups eg - WHERE eg.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expense_groups', rcount; - - DELETE - FROM employee_mappings em - WHERE em.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % employee_mappings', rcount; - - DELETE - FROM category_mappings cm - WHERE cm.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % category_mappings', rcount; - - DELETE - FROM mappings m - WHERE m.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % mappings', rcount; - - DELETE - FROM mapping_settings ms - WHERE ms.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % mapping_settings', rcount; - - DELETE - FROM general_mappings gm - WHERE gm.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % general_mappings', rcount; - - DELETE - FROM workspace_general_settings wgs - WHERE wgs.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % workspace_general_settings', rcount; - - DELETE - FROM expense_group_settings egs - WHERE egs.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expense_group_settings', rcount; - - DELETE - FROM expense_fields ef - WHERE ef.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expense_fields', rcount; - - DELETE - FROM fyle_credentials fc - WHERE fc.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % fyle_credentials', rcount; - - DELETE - FROM qbo_credentials qc - WHERE qc.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % qbo_credentials', rcount; - - DELETE - FROM expense_attributes ea - WHERE ea.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expense_attributes', rcount; - - DELETE - FROM expense_filters ef - WHERE ef.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % expense_filters', rcount; - - DELETE - FROM destination_attributes da - WHERE da.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % destination_attributes', rcount; - - DELETE - FROM workspace_schedules wsch - WHERE wsch.workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % workspace_schedules', rcount; - - DELETE - FROM django_q_schedule dqs - WHERE dqs.args = _workspace_id::varchar(255); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % django_q_schedule', rcount; - - DELETE - FROM auth_tokens aut - WHERE aut.user_id IN ( - SELECT u.id FROM users u WHERE u.id IN ( - SELECT wu.user_id FROM workspaces_user wu WHERE workspace_id = _workspace_id - ) - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % auth_tokens', rcount; - - DELETE - FROM workspaces_user wu - WHERE workspace_id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % workspaces_user', rcount; - - DELETE - FROM users u - WHERE u.id IN ( - SELECT wu.user_id FROM workspaces_user wu WHERE workspace_id = _workspace_id - ); - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % users', rcount; - - DELETE - FROM workspaces w - WHERE w.id = _workspace_id; - GET DIAGNOSTICS rcount = ROW_COUNT; - RAISE NOTICE 'Deleted % workspaces', rcount; - -RETURN; -END -$$ LANGUAGE plpgsql; diff --git a/sql/functions/delete-workspace.sql b/sql/functions/delete-workspace.sql index f11fc8f9..1afc4f0c 100644 --- a/sql/functions/delete-workspace.sql +++ b/sql/functions/delete-workspace.sql @@ -3,6 +3,7 @@ DROP FUNCTION if exists delete_workspace; CREATE OR REPLACE FUNCTION delete_workspace(IN _workspace_id integer) RETURNS void AS $$ DECLARE rcount integer; + _org_id varchar(255); BEGIN RAISE NOTICE 'Deleting data from workspace % ', _workspace_id; @@ -140,11 +141,7 @@ BEGIN DELETE FROM expenses e - WHERE e.id IN ( - SELECT expense_id FROM expense_groups_expenses ege WHERE ege.expensegroup_id IN ( - SELECT eg.id FROM expense_groups eg WHERE eg.workspace_id = _workspace_id - ) - ); + WHERE e.workspace_id = _workspace_id; GET DIAGNOSTICS rcount = ROW_COUNT; RAISE NOTICE 'Deleted % expenses', rcount; @@ -210,6 +207,12 @@ BEGIN GET DIAGNOSTICS rcount = ROW_COUNT; RAISE NOTICE 'Deleted % expense_group_settings', rcount; + DELETE + FROM expense_fields ef + WHERE ef.workspace_id = _workspace_id; + GET DIAGNOSTICS rcount = ROW_COUNT; + RAISE NOTICE 'Deleted % expense_fields', rcount; + DELETE FROM fyle_credentials fc WHERE fc.workspace_id = _workspace_id; @@ -276,12 +279,17 @@ BEGIN GET DIAGNOSTICS rcount = ROW_COUNT; RAISE NOTICE 'Deleted % users', rcount; + _org_id := (SELECT fyle_org_id FROM workspaces WHERE id = _workspace_id); + DELETE FROM workspaces w WHERE w.id = _workspace_id; GET DIAGNOSTICS rcount = ROW_COUNT; RAISE NOTICE 'Deleted % workspaces', rcount; + RAISE NOTICE E'\n\n\n\n\n\n\n\n\nSwitch to integration_settings db and run the below query to delete the integration'; + RAISE NOTICE E'select delete_integration(''%'');\n\n\n\n\n\n\n\n\n\n\n', _org_id; + RETURN; END $$ LANGUAGE plpgsql; diff --git a/sql/scripts/020-fill-org-id-expenses.sql b/sql/scripts/020-fill-org-id-expenses.sql new file mode 100644 index 00000000..1df08fd6 --- /dev/null +++ b/sql/scripts/020-fill-org-id-expenses.sql @@ -0,0 +1,14 @@ +rollback; +begin; + +with expense_groups as ( + select w.fyle_org_id, e.id from expenses e + join expense_groups_expenses ege on e.id = ege.expense_id + join expense_groups eg on eg.id = ege.expensegroup_id + join workspaces w on w.id = eg.workspace_id + where e.org_id is null +) +update expenses e +set org_id = eg.fyle_org_id +from expense_groups eg +where e.id = eg.id; diff --git a/sql/scripts/021-fill-workspace-id-expenses.sql b/sql/scripts/021-fill-workspace-id-expenses.sql new file mode 100644 index 00000000..e59512be --- /dev/null +++ b/sql/scripts/021-fill-workspace-id-expenses.sql @@ -0,0 +1,11 @@ +rollback; +begin; + +with workspace as ( + select w.fyle_org_id, w.id, e.id as expense_pk from expenses e + join workspaces w on w.fyle_org_id = e.org_id +) +update expenses e +set workspace_id = w.id +from workspace w +where e.id = w.expense_pk; diff --git a/sql/scripts/022-fill-skipped-accounting-export-summary.sql b/sql/scripts/022-fill-skipped-accounting-export-summary.sql new file mode 100644 index 00000000..6bf6ce88 --- /dev/null +++ b/sql/scripts/022-fill-skipped-accounting-export-summary.sql @@ -0,0 +1,10 @@ +rollback; +begin; + +update expenses set accounting_export_summary = jsonb_build_object( + 'id', expense_id, + 'url', 'https://quickbooks-new.fyleapps.tech/workspaces/main/export_log', + 'state', 'SKIPPED', + 'synced', false, + 'error_type', null +) where is_skipped = 't'; diff --git a/tests/sql_fixtures/reset_db_fixtures/reset_db.sql b/tests/sql_fixtures/reset_db_fixtures/reset_db.sql index ac99f637..1434e100 100644 --- a/tests/sql_fixtures/reset_db_fixtures/reset_db.sql +++ b/tests/sql_fixtures/reset_db_fixtures/reset_db.sql @@ -2,8 +2,8 @@ -- PostgreSQL database dump -- --- Dumped from database version 15.3 (Debian 15.3-1.pgdg120+1) --- Dumped by pg_dump version 15.3 (Debian 15.3-1.pgdg100+1) +-- Dumped from database version 15.4 (Debian 15.4-1.pgdg120+1) +-- Dumped by pg_dump version 15.4 (Debian 15.4-1.pgdg100+1) SET statement_timeout = 0; SET lock_timeout = 0; @@ -932,7 +932,10 @@ CREATE TABLE public.expenses ( payment_number character varying(55), is_skipped boolean, report_title text, - posted_at timestamp with time zone + posted_at timestamp with time zone, + accounting_export_summary jsonb NOT NULL, + workspace_id integer, + previous_export_state character varying(255) ); @@ -3906,6 +3909,9 @@ COPY public.django_migrations (id, app, name, applied) FROM stdin; 166 fyle 0030_expense_posted_at 2023-06-21 12:52:35.732825+00 167 fyle 0031_auto_20230801_1215 2023-08-03 10:31:12.876198+00 168 workspaces 0042_workspacegeneralsettings_name_in_journal_entry 2023-08-03 10:31:12.887514+00 +169 fyle 0032_expense_accounting_export_summary 2023-09-04 09:16:33.110573+00 +170 fyle 0033_expense_workspace 2023-09-05 12:23:23.807031+00 +171 fyle 0034_expense_previous_export_state 2023-09-07 12:40:23.1024+00 \. @@ -33209,67 +33215,67 @@ COPY public.expense_groups_expenses (id, expensegroup_id, expense_id) FROM stdin -- Data for Name: expenses; Type: TABLE DATA; Schema: public; Owner: postgres -- -COPY public.expenses (id, employee_email, category, sub_category, project, expense_id, expense_number, claim_number, amount, currency, foreign_amount, foreign_currency, settlement_id, reimbursable, exported, state, vendor, cost_center, purpose, report_id, spent_at, approved_at, expense_created_at, expense_updated_at, created_at, updated_at, fund_source, custom_properties, verified_at, billable, paid_on_qbo, org_id, tax_amount, tax_group_id, file_ids, corporate_card_id, employee_name, payment_number, is_skipped, report_title, posted_at) FROM stdin; -1 ashwin.t@fyle.in Food \N Acera txZfwIJJn6pI E/2022/05/T/1 C/2022/05/R/1 5 INR \N \N setiYZAyfK7Ye t f PAYMENT_PROCESSING \N 01: San Francisco \N rpVBizZpEAos 2022-05-17 06:30:00+00 2022-05-17 15:52:10.498+00 2022-05-17 15:51:40.157566+00 2022-05-17 15:52:30.923957+00 2022-05-23 06:25:54.1468+00 2022-05-23 06:25:54.14685+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "", "Netsuite Sage Intacct Customer Qbo Department Hey": ""} \N \N f orOarwdPeIWs \N \N {} \N Joanna P/2022/05/R/1 f \N \N -2 ashwin.t@fyle.in Food \N Acera txRs11LMbHq7 E/2022/03/T/2 C/2022/03/R/2 999 INR \N \N setcWknPMAMeB t f PAYMENT_PROCESSING \N \N \N rp4odQDVFNBP 2022-03-09 06:30:00+00 2022-03-09 05:10:39.65+00 2022-03-09 05:09:55.106443+00 2022-05-17 07:16:31.559048+00 2022-05-23 06:25:54.18655+00 2022-05-23 06:25:54.186599+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "United Kingdom"} \N \N f orOarwdPeIWs 47.57 tg33IC8PoNNb {} \N Ashwin P/2022/03/R/2 f \N \N -3 ashwin.t@fyle.in Taxi \N Acera txNj2N2NRJPw E/2022/03/T/1 C/2022/03/R/1 2000 INR \N \N setUXr52IorxG t f PAYMENT_PROCESSING Uber \N \N rpBKhrCoEt0D 2022-03-08 06:30:00+00 2022-03-08 17:20:27.527+00 2022-03-08 17:19:09.169854+00 2022-05-17 07:16:29.41957+00 2022-05-23 06:25:54.2088+00 2022-05-23 06:25:54.208855+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "Bir Billing"} \N \N f orOarwdPeIWs 95.24 tg33IC8PoNNb {} \N Ashwin P/2022/03/R/1 f \N \N -4 ashwin.t@fyle.in Food \N Aaron Abbott txj8kWkDTyog E/2022/05/T/17 C/2022/05/R/5 1 USD \N \N set9k3fC23ByK f f PAYMENT_PROCESSING Ashwin Marketing \N rpH0YbevEADk 2022-05-17 17:00:00+00 2022-05-17 12:40:21.481+00 2022-05-17 12:37:46.724529+00 2022-05-17 12:43:32.838356+00 2022-05-23 11:11:27.90706+00 2022-05-23 11:11:27.907251+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 0.22 tggu76WXIdjY {} \N Joanna P/2022/05/R/8 f \N \N -5 user9@fyleforgotham.in Food \N Project 1 txHmoggWDQZs E/2021/04/T/310 C/2021/04/R/42 801 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N SME \N rp0jIc6urRHS 2020-05-10 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:37.727141+00 2022-05-17 07:17:23.922707+00 2022-05-23 11:11:27.923435+00 2022-05-23 11:11:27.923467+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiO3nQ5ZxnjJ} \N Justin Glass P/2022/05/R/4 f \N \N -6 user8@fyleforgotham.in Office Supplies \N Project 9 txjFtOcEBO4C E/2021/04/T/277 C/2021/04/R/38 383 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Sales and Cross \N rpXV3IIN7LMJ 2020-05-06 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:23.409596+00 2022-05-17 07:17:23.922707+00 2022-05-23 11:11:27.940168+00 2022-05-23 11:11:27.940202+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiMCOjeTtZJn} \N Jessica Lane P/2022/05/R/3 f \N \N -7 user9@fyleforgotham.in Office Party \N Project 8 txU2qpKmrUR9 E/2021/04/T/312 C/2021/04/R/42 1188 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Strategy Planning \N rp0jIc6urRHS 2020-05-13 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:41.896287+00 2022-05-17 07:17:23.922707+00 2022-05-23 11:11:27.955311+00 2022-05-23 11:11:27.955345+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fik0BjLHFYpc} \N Justin Glass P/2022/05/R/4 f \N \N -8 user9@fyleforgotham.in Office Party \N Project 4 tx3o8Yb29JVH E/2021/04/T/314 C/2021/04/R/42 701 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Retail \N rp0jIc6urRHS 2020-05-05 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:46.065774+00 2022-05-17 07:17:16.050882+00 2022-05-23 11:11:27.972451+00 2022-05-23 11:11:27.972485+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiBqn8QEjQoX} \N Justin Glass P/2022/05/R/4 f \N \N -9 ashwin.t@fyle.in Food \N Aaron Abbott txNEnr2OEtkm E/2021/12/T/36 C/2021/12/R/36 18.46 USD 1371 INR setGtUuWv5015 f f PAYMENT_PROCESSING Shiv Sagar Marketing \N rpjdzTobT8VT 2021-09-30 17:00:00+00 2021-12-29 15:15:49.162+00 2021-12-29 15:15:26.659581+00 2022-05-17 07:16:58.388588+00 2022-05-23 11:11:27.989462+00 2022-05-23 11:11:27.989498+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 4.04 \N {fi9pLK99HpFq} \N Ashwin P/2022/05/R/6 f \N \N -10 user8@fyleforgotham.in Office Party \N Project 7 tx77CqtMUW2L E/2021/04/T/282 C/2021/04/R/38 797 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Marketing \N rpXV3IIN7LMJ 2020-05-01 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:33.736778+00 2022-05-17 07:16:54.275934+00 2022-05-23 11:11:28.004384+00 2022-05-23 11:11:28.004418+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiG8Ra8UGHNF} \N Jessica Lane P/2022/05/R/3 f \N \N -11 user9@fyleforgotham.in Office Supplies \N Project 9 txJFfGeWSQdD E/2021/04/T/308 C/2021/04/R/42 608 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Sales and Cross \N rp0jIc6urRHS 2020-05-03 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:33.627277+00 2022-05-17 07:16:54.275934+00 2022-05-23 11:11:28.017447+00 2022-05-23 11:11:28.017479+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiF1M3jRGeMq} \N Justin Glass P/2022/05/R/4 f \N \N -12 user9@fyleforgotham.in Office Party \N Project 1 txqy5WraeWt6 E/2021/04/T/309 C/2021/04/R/42 1803 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Retail \N rp0jIc6urRHS 2020-05-01 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:35.669916+00 2022-05-17 07:16:46.248509+00 2022-05-23 11:11:28.034208+00 2022-05-23 11:11:28.034243+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiGqUizprTME} \N Justin Glass P/2022/05/R/4 f \N \N -13 user9@fyleforgotham.in Food \N Project 4 txUve21xzdtr E/2021/04/T/311 C/2021/04/R/42 1104 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Legal and Secretarial \N rp0jIc6urRHS 2020-05-11 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:39.719515+00 2022-05-17 07:16:46.248509+00 2022-05-23 11:11:28.048945+00 2022-05-23 11:11:28.048978+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fizeHWCFAS9Y} \N Justin Glass P/2022/05/R/4 f \N \N -14 user9@fyleforgotham.in Food \N Project 4 tx4q4RJTeYBw E/2021/04/T/313 C/2021/04/R/42 642 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Internal \N rp0jIc6urRHS 2020-05-07 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:43.931468+00 2022-05-17 07:16:45.527217+00 2022-05-23 11:11:28.065481+00 2022-05-23 11:11:28.06552+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fioo7Iifocss} \N Justin Glass P/2022/05/R/4 f \N \N -15 user8@fyleforgotham.in Software \N Project 9 txYgJFgb8MET E/2021/04/T/281 C/2021/04/R/38 1007 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Sales and Cross \N rpXV3IIN7LMJ 2020-05-12 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:31.69043+00 2022-05-17 07:16:36.602763+00 2022-05-23 11:11:28.084189+00 2022-05-23 11:11:28.084227+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fi1ftfZiR2yH} \N Jessica Lane P/2022/05/R/3 f \N \N -16 user8@fyleforgotham.in Food \N Project 10 txgryOAgCf9b E/2021/04/T/283 C/2021/04/R/38 727 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Marketing \N rpXV3IIN7LMJ 2020-05-03 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:36.010173+00 2022-05-17 07:16:35.584167+00 2022-05-23 11:11:28.10159+00 2022-05-23 11:11:28.101782+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiDrNR9MnsnG} \N Jessica Lane P/2022/05/R/3 f \N \N -17 user9@fyleforgotham.in Others \N Project 1 tx5PXU8lacAv E/2021/04/T/307 C/2021/04/R/42 592 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Retail \N rp0jIc6urRHS 2020-05-01 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:31.337996+00 2022-05-17 07:15:15.733851+00 2022-05-23 11:11:28.121484+00 2022-05-23 11:11:28.121649+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiS51iv21cIP} \N Justin Glass P/2022/05/R/4 f \N \N -18 user8@fyleforgotham.in Food \N Project 10 txgXWggJGBf0 E/2021/04/T/279 C/2021/04/R/38 1390 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Strategy Planning \N rpXV3IIN7LMJ 2020-05-10 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:27.541116+00 2022-05-17 07:15:15.733851+00 2022-05-23 11:11:28.135911+00 2022-05-23 11:11:28.135993+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fi3JOklBstEc} \N Jessica Lane P/2022/05/R/3 f \N \N -19 user8@fyleforgotham.in Office Party \N Project 6 txladt6mN4nj E/2021/04/T/280 C/2021/04/R/38 -352 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Retail \N rpXV3IIN7LMJ 2020-05-07 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:29.655549+00 2022-05-17 07:15:15.733851+00 2022-05-23 11:11:28.150877+00 2022-05-23 11:11:28.150914+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiS2JucojE2n} \N Jessica Lane P/2022/05/R/3 f \N \N -20 user4@fyleforgotham.in Software \N Project 6 txC5usIbRgRg E/2021/04/T/167 C/2021/04/R/23 673 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N Sales and Cross \N rp0l2lFIQ0Uk 2020-05-10 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:12.5464+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.165178+00 2022-05-23 11:11:28.165211+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiF0RDE9b8Or} \N Samantha Washington P/2022/05/R/5 f \N \N -21 user4@fyleforgotham.in Software \N Project 4 txh7HByDjFXT E/2021/04/T/165 C/2021/04/R/23 608 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N Audit \N rp0l2lFIQ0Uk 2020-05-07 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:08.445843+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.179708+00 2022-05-23 11:11:28.17974+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiDcXmeSsyvA} \N Samantha Washington P/2022/05/R/5 f \N \N -22 user4@fyleforgotham.in Software \N Project 6 txk9w63xZYyA E/2021/04/T/168 C/2021/04/R/23 900 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N Strategy Planning \N rp0l2lFIQ0Uk 2020-05-04 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:14.725066+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.195923+00 2022-05-23 11:11:28.195955+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiEFya0Ia9bI} \N Samantha Washington P/2022/05/R/5 f \N \N -23 user4@fyleforgotham.in Office Party \N Project 7 txRrLLI8zD4k E/2021/04/T/166 C/2021/04/R/23 922 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N SME \N rp0l2lFIQ0Uk 2020-05-14 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:10.448385+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.210457+00 2022-05-23 11:11:28.210501+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fitQHS1kdC25} \N Samantha Washington P/2022/05/R/5 f \N \N -24 ashwin.t@fyle.in Taxi \N Aaron Abbott txgUAIXUPQ8r E/2022/05/T/15 C/2022/05/R/4 100 USD \N \N setDiksMn83K7 f f PAYMENT_PROCESSING Allison Hill Marketing \N rpViBmuYmAgw 2022-05-13 17:00:00+00 2022-05-13 09:30:13.484+00 2022-05-13 09:29:19.925312+00 2022-05-13 09:32:06.643941+00 2022-05-23 11:11:28.227764+00 2022-05-23 11:11:28.227804+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh \N \N {} \N Joanna P/2022/05/R/7 f \N \N -25 ashwin.t@fyle.in Food \N Aaron Abbott txxTi9ZfdepC E/2022/05/T/16 C/2022/05/R/4 50 USD \N \N setDiksMn83K7 t f PAYMENT_PROCESSING Ashwin Marketing \N rpViBmuYmAgw 2022-05-13 17:00:00+00 2022-05-13 09:30:13.484+00 2022-05-13 09:29:43.535468+00 2022-05-13 09:32:06.643941+00 2022-05-23 11:11:28.241406+00 2022-05-23 11:11:28.24144+00 PERSONAL {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh \N \N {} \N Joanna P/2022/05/R/7 f \N \N -26 sravan.kumar@fyle.in WIP \N Bebe Rexha txGTyHWrmv7s E/2022/05/T/8 C/2022/05/R/9 10 USD \N \N setdKjMoHS47d t f PAYMENT_PROCESSING \N Adidas \N rpDA8I6af7K2 2022-05-23 17:00:00+00 2022-05-23 04:29:46.308+00 2022-05-23 04:28:41.961851+00 2022-05-23 04:29:59.754485+00 2022-05-23 11:35:09.675594+00 2022-05-23 11:35:09.675634+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fieQIOTwDiXj} \N sravan k P/2022/05/R/7 f \N \N -27 sravan.kumar@fyle.in WIP \N Bebe Rexha txj6oP5vEevy E/2022/05/T/9 C/2022/05/R/9 77 USD \N \N setdKjMoHS47d f f PAYMENT_PROCESSING \N Adidas \N rpDA8I6af7K2 2022-05-23 17:00:00+00 2022-05-23 04:29:46.308+00 2022-05-23 04:28:55.139059+00 2022-05-23 04:29:59.754485+00 2022-05-23 11:35:09.69751+00 2022-05-23 11:35:09.697545+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fiUVyx8HQ30G} \N sravan k P/2022/05/R/7 f \N \N -28 sravan.kumar@fyle.in WIP \N Bebe Rexha tx3i1mrGprDs E/2022/05/T/7 C/2022/05/R/8 1 USD \N \N set2S6CqU59C1 f f PAYMENT_PROCESSING \N Adidas \N rp1dtMICkdqU 2022-05-23 17:00:00+00 2022-05-23 04:22:33.747+00 2022-05-23 04:22:19.588463+00 2022-05-23 04:23:50.476021+00 2022-05-23 11:35:09.718808+00 2022-05-23 11:35:09.718846+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fiLO4HUsnbik} \N sravan k P/2022/05/R/6 f \N \N -29 sravan.kumar@fyle.in Courier \N Bebe Rexha txglJd1GPCXm E/2022/04/T/182 C/2022/05/R/2 1 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpk22gODSyaY 2022-04-26 17:00:00+00 2022-05-20 13:35:18.576+00 2022-04-26 11:59:43.384164+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.733256+00 2022-05-23 11:35:09.733313+00 PERSONAL {"Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fi12kYip1LCk} \N sravan k P/2022/05/R/5 f \N \N -30 sravan.kumar@fyle.in WIP \N Bebe Rexha txh3QPqFZE47 E/2022/04/T/183 C/2022/04/R/34 1 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpVjuHvS0wx5 2022-04-27 17:00:00+00 2022-04-27 05:28:24.659+00 2022-04-27 05:16:57.805222+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.749358+00 2022-05-23 11:35:09.749437+00 PERSONAL {"Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/5 f \N \N -31 sravan.kumar@fyle.in WIP \N Bebe Rexha txJThyugq3dO E/2022/05/T/4 C/2022/05/R/4 20 USD \N \N setcAs1ng0qTT f f PAYMENT_PROCESSING \N Adidas \N rpOuhx4SIOD8 2022-05-21 17:00:00+00 2022-05-21 03:33:05.796+00 2022-05-21 03:32:51.691013+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.768353+00 2022-05-23 11:35:09.768393+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/5 f \N \N -32 sravan.kumar@fyle.in WIP \N Bebe Rexha txNGSSLoRUiq E/2022/05/T/2 C/2022/05/R/3 1 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpDdPtQu8Leg 2021-08-07 17:00:00+00 2022-05-20 13:35:45.95+00 2022-05-20 13:34:43.935588+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.787782+00 2022-05-23 11:35:09.787815+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/5 f \N \N -33 sravan.kumar@fyle.in WIP \N Bebe Rexha txW6r1wTHCQr E/2022/05/T/6 C/2022/05/R/6 96 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpawE81idoYo 2022-05-23 17:00:00+00 2022-05-23 03:50:32.697+00 2022-05-23 03:47:39.0044+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.807184+00 2022-05-23 11:35:09.807218+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fiwZAHxieaWd} \N sravan k P/2022/05/R/5 f \N \N -34 sravan.kumar@fyle.in WIP \N Bebe Rexha txXy5Prbt9iH E/2022/05/T/1 C/2022/05/R/1 10 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpmabEGhBlGo 2022-05-02 17:00:00+00 2022-05-02 11:47:18.267+00 2022-05-02 11:42:40.798011+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.825094+00 2022-05-23 11:35:09.825131+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fi6LddX3MBgw} \N sravan k P/2022/05/R/5 f \N \N -35 ashwin.t@fyle.in Food \N \N txeSMU6QuRpW E/2022/05/T/10 C/2022/05/R/10 1 USD \N \N setEwmx4RfC56 t f PAYMENT_PROCESSING \N \N \N rpm6D48AcjKK 2022-05-23 17:00:00+00 2022-05-23 11:38:40.875+00 2022-05-23 11:38:18.295764+00 2022-05-23 11:39:08.507389+00 2022-05-23 12:31:23.914452+00 2022-05-23 12:31:23.914502+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N Joanna P/2022/05/R/8 f \N \N -36 ashwin.t@fyle.in Flight \N Aaron Abbott txW7qE5DUF82 E/2022/05/T/20 C/2022/05/R/6 5 USD \N \N setCb41PcrHmO t f PAYMENT_PROCESSING Ashwin \N \N rpYkgyHrGcg8 2022-05-23 17:00:00+00 2022-05-23 13:34:56.716+00 2022-05-23 13:32:45.769742+00 2022-05-23 13:37:01.524157+00 2022-05-23 13:37:17.821638+00 2022-05-23 13:37:17.821685+00 PERSONAL {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 1.09 tggu76WXIdjY {} \N Joanna P/2022/05/R/9 f \N \N -37 ashwin.t@fyle.in Flight \N Aaron Abbott tx4w7YXZV1fa E/2022/05/T/21 C/2022/05/R/7 11 USD \N \N setlcYd0kfoBv f f PAYMENT_PROCESSING Ashwin Marketing \N rp5uAOkQovrD 2022-05-23 17:00:00+00 2022-05-23 17:31:49.913+00 2022-05-23 17:29:33.605913+00 2022-05-23 17:32:19.370016+00 2022-05-23 17:35:13.712115+00 2022-05-23 17:35:13.712159+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 2.41 tggu76WXIdjY {fihTwZoMuRVN} \N Joanna P/2022/05/R/10 f \N \N -38 ashwin.t@fyle.in Food \N \N txP7sCqGUYnA E/2022/05/T/3 C/2022/05/R/3 41 INR \N \N setLD0YF7n0QG t f PAYMENT_PROCESSING \N 01: San Francisco \N rp5R0j7JNtVu 2022-05-23 06:30:00+00 2022-05-23 11:07:18.467+00 2022-05-23 11:06:58.305532+00 2022-05-23 11:08:33.781939+00 2022-05-25 14:36:48.708012+00 2022-05-25 14:36:48.708056+00 PERSONAL {"Jilkiplk": "", "Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "", "Netsuite Sage Intacct Customer Qbo Department Hey": ""} \N f f orOarwdPeIWs \N \N {fiWcpsmmgnac} \N Joanna P/2022/05/R/3 f \N \N -39 ashwin.t@fyle.in Food \N \N txpGmLpiUhNk E/2022/05/T/2 C/2022/05/R/2 23 INR \N \N set8xNhjXpvTC t f PAYMENT_PROCESSING \N 01: San Francisco \N rpF3G59kmLmX 2022-05-23 06:30:00+00 2022-05-23 10:42:16.038+00 2022-05-23 10:42:02.422005+00 2022-05-23 10:42:31.331925+00 2022-05-25 14:36:48.747386+00 2022-05-25 14:36:48.747422+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "", "Netsuite Sage Intacct Customer Qbo Department Hey": ""} \N f f orOarwdPeIWs \N \N {fij5zhC5i0Ny} \N Joanna P/2022/05/R/2 f \N \N -40 sravan.kumar@fyle.in Food \N \N txjIqTCtkkC8 E/2022/05/T/21 C/2022/05/R/18 100 USD \N \N set3ZMFXrDPL3 f f PAYMENT_PROCESSING \N \N \N rpLawO11bFib 2022-05-25 17:00:00+00 2022-05-25 08:59:25.649+00 2022-05-25 08:59:07.718891+00 2022-05-25 09:04:05.66983+00 2022-05-25 14:38:34.641061+00 2022-05-25 14:38:34.641096+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N f f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/16 f \N \N -41 sravan.kumar@fyle.in WIP \N Bebe Rexha txUPRc3VwxOP E/2022/05/T/19 C/2022/05/R/17 101 USD \N \N setb1pSLMIok8 f f PAYMENT_PROCESSING \N Adidas \N rpv1txzAsgr3 2021-01-01 17:00:00+00 2022-05-25 07:24:12.987+00 2022-05-25 07:21:40.598113+00 2022-05-25 07:25:00.848892+00 2022-05-25 14:38:34.653781+00 2022-05-25 14:38:34.653817+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/15 f \N \N -42 ashwin.t@fyle.in Food \N \N txUDvDmEV4ep E/2022/05/T/18 C/2022/05/R/16 5 USD \N \N set33iAVXO7BA t f PAYMENT_PROCESSING \N \N \N rpE2JyATZhDe 2020-05-25 17:00:00+00 2022-05-25 06:05:23.362+00 2022-05-25 06:04:46.557927+00 2022-05-25 06:05:47.36985+00 2022-05-25 14:38:34.664871+00 2022-05-25 14:38:34.664906+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N Joanna P/2022/05/R/14 f \N \N -43 sravan.kumar@fyle.in WIP \N Bebe Rexha tx1FW3uxYZG6 E/2022/05/T/16 C/2022/05/R/15 151 USD \N \N setzFn3FK5t80 f f PAYMENT_PROCESSING \N Adidas \N rprwGgzOZyfR 2022-05-25 17:00:00+00 2022-05-25 03:41:49.042+00 2022-05-25 03:41:28.839711+00 2022-05-25 03:42:10.145663+00 2022-05-25 14:38:34.680226+00 2022-05-25 14:38:34.680352+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/13 f \N \N -44 sravan.kumar@fyle.in WIP \N Bebe Rexha txVXhyVB8mgK E/2022/05/T/15 C/2022/05/R/14 45 USD \N \N setsN8cLD9KIn f f PAYMENT_PROCESSING \N Adidas \N rpnG3lZYDsHU 2022-05-25 17:00:00+00 2022-05-25 02:48:53.791+00 2022-05-25 02:48:37.432989+00 2022-05-25 02:49:18.189037+00 2022-05-25 14:38:34.700082+00 2022-05-25 14:38:34.700127+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/12 f \N \N -45 sravan.kumar@fyle.in WIP \N Bebe Rexha txBMQRkBQciI E/2022/05/T/14 C/2022/05/R/13 10 USD \N \N setanDKqMZfXB f f PAYMENT_PROCESSING \N Adidas \N rpVvNQvE2wbm 2022-05-25 17:00:00+00 2022-05-25 02:38:40.858+00 2022-05-25 02:38:25.832419+00 2022-05-25 02:39:08.208877+00 2022-05-25 14:38:34.713384+00 2022-05-25 14:38:34.713419+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/11 f \N \N -46 sravan.kumar@fyle.in WIP \N Bebe Rexha txkw3dt3umkN E/2022/05/T/12 C/2022/05/R/12 101 USD \N \N setBe6qAlNXPU f f PAYMENT_PROCESSING \N Adidas \N rp5lITpxFLxE 2022-05-24 17:00:00+00 2022-05-24 15:59:13.26+00 2022-05-24 15:55:50.369024+00 2022-05-24 16:00:27.982+00 2022-05-25 14:38:34.728474+00 2022-05-25 14:38:34.728523+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/10 f \N \N -47 sravan.kumar@fyle.in WIP \N Bebe Rexha tx75COnDBjXm E/2022/05/T/11 C/2022/05/R/11 65 USD \N \N setHwMulHK6X3 t f PAID \N Adidas \N rp3YxnytLrgS 2021-01-11 17:00:00+00 2022-05-24 13:54:57.723+00 2022-05-24 13:53:31.773336+00 2022-05-24 15:54:56.794866+00 2022-05-25 14:38:34.744673+00 2022-05-25 14:38:34.744709+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/9 f \N \N -48 ashwin.t@fyle.in Bus \N \N txGtk31AfEY5 E/2022/04/T/16 C/2022/04/R/15 1 INR \N \N setbb8jxonU9n t f PAYMENT_PROCESSING \N \N \N rplJh22whCmG 2022-04-08 06:30:00+00 2022-04-08 09:24:49.042+00 2022-04-08 09:24:22.738403+00 2022-05-24 07:14:36.820174+00 2022-05-25 14:47:48.349048+00 2022-05-25 14:47:48.349141+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/04/R/14 f \N \N -49 ashwin.t@fyle.in Bus \N \N txhXSn5F9uZ4 E/2022/04/T/12 C/2022/04/R/11 1 INR \N \N setHIFl6CHqi7 t f PAYMENT_PROCESSING \N \N \N rpZAGw9was22 2022-04-07 06:30:00+00 2022-04-07 11:15:46.665+00 2022-04-07 11:15:26.848334+00 2022-05-24 07:14:28.930784+00 2022-05-25 14:47:48.382086+00 2022-05-25 14:47:48.382123+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/04/R/10 f \N \N -50 ashwin.t@fyle.in Bus \N \N txJGT0u4l47D E/2022/04/T/11 C/2022/04/R/10 22 INR \N \N setIfAz8a7Uqs t f PAYMENT_PROCESSING \N Cost Center \N rp3AJwfsGBEZ 2022-04-07 06:30:00+00 2022-04-07 11:13:19.68+00 2022-04-07 11:12:56.790163+00 2022-05-24 07:14:28.68168+00 2022-05-25 14:47:48.40861+00 2022-05-25 14:47:48.408658+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/04/R/9 f \N \N -51 ashwin.t@fyle.in Bus \N \N txu2bPK9QtSP E/2022/04/T/7 C/2022/04/R/6 -11 INR \N \N setctmftpoQTr t f PAYMENT_PROCESSING \N Cost Center \N rpeNhwlQn2fb 2022-04-06 06:30:00+00 2022-04-06 17:10:15.355+00 2022-04-06 17:09:27.222098+00 2022-05-17 07:18:10.821355+00 2022-05-25 14:47:48.432631+00 2022-05-25 14:47:48.432666+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Ashwin P/2022/04/R/6 f \N \N -52 ashwin.t@fyle.in Bus \N \N txjjSTAhBXsw E/2022/04/T/1 C/2022/04/R/1 1 INR \N \N setoxDCmYrJvK t f PAYMENT_PROCESSING \N Cost Center \N rpP6feukA57q 2022-04-06 06:30:00+00 2022-04-06 11:15:43.619+00 2022-04-06 11:15:06.419141+00 2022-05-17 07:18:05.368491+00 2022-05-25 14:47:48.468033+00 2022-05-25 14:47:48.46831+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0.04 tg1MoZ63tdCg {} \N Ashwin P/2022/04/R/1 f \N \N -53 ashwin.t@fyle.in Bus \N \N txhJmYEqGRT8 E/2022/04/T/13 C/2022/04/R/12 200 INR \N \N setulC9Vi6zXD t f PAYMENT_PROCESSING \N Cost Center \N rpOiZLTpX4OI 2022-04-08 06:30:00+00 2022-04-08 05:29:38.258+00 2022-04-08 05:28:59.489277+00 2022-05-17 07:17:55.683792+00 2022-05-25 14:47:48.574082+00 2022-05-25 14:47:48.574132+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 21.43 tg4OD3Ig57FK {} \N Ashwin P/2022/04/R/11 f \N \N -54 ashwin.t@fyle.in Bus \N \N txtlNGpZAL8n E/2022/04/T/15 C/2022/04/R/14 200 INR \N \N setOgaLUS52QZ t f PAYMENT_PROCESSING \N \N \N rp6aB6XQhMPb 2022-04-08 06:30:00+00 2022-04-08 08:06:31.754+00 2022-04-08 08:05:52.171087+00 2022-05-17 07:17:55.683792+00 2022-05-25 14:47:48.6507+00 2022-05-25 14:47:48.650737+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Ashwin P/2022/04/R/13 f \N \N -55 ashwin.t@fyle.in Bus \N \N txPsADTUzf2I E/2022/04/T/9 C/2022/04/R/8 1 INR \N \N setNZqYFdQib8 t f PAYMENT_PROCESSING \N Cost Center \N rpuoFNcLAKMc 2022-04-07 06:30:00+00 2022-04-07 09:15:05.368+00 2022-04-07 09:14:28.50854+00 2022-05-17 07:17:54.45687+00 2022-05-25 14:47:48.680265+00 2022-05-25 14:47:48.680359+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0.11 tg4OD3Ig57FK {} \N Ashwin P/2022/04/R/8 f \N \N -56 ashwin.t@fyle.in Bus \N \N tx9FusYWBQMW E/2022/04/T/5 C/2022/04/R/5 1 INR \N \N settQTSdGcOKI t f PAYMENT_PROCESSING \N \N \N rpZxs9qFrWic 2022-04-06 06:30:00+00 2022-04-06 11:51:58.671+00 2022-04-06 11:51:19.394765+00 2022-05-17 07:17:49.188761+00 2022-05-25 14:47:48.722995+00 2022-05-25 14:47:48.723131+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0.11 tg4OD3Ig57FK {} \N Ashwin P/2022/04/R/5 f \N \N -57 ashwin.t@fyle.in Bus \N \N tx0SDpb0Tkgw E/2022/04/T/3 C/2022/04/R/3 1 INR \N \N setZ1yiEV5aaH t f PAYMENT_PROCESSING \N Cost Center \N rpHKjTkMnXH3 2022-04-06 06:30:00+00 2022-04-06 11:36:27.241+00 2022-04-06 11:35:57.496504+00 2022-05-17 07:17:49.041897+00 2022-05-25 14:47:48.74943+00 2022-05-25 14:47:48.749466+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Ashwin P/2022/04/R/3 f \N \N -58 ashwin.t@fyle.in Bus \N \N txRJEOyCAW6i E/2022/04/T/4 C/2022/04/R/4 1 INR \N \N set4OQIR1sSeA t f PAYMENT_PROCESSING \N Cost Center \N rpXWxpSaeyEE 2022-04-06 06:30:00+00 2022-04-06 11:43:31.356+00 2022-04-06 11:42:39.66612+00 2022-05-17 07:17:49.041897+00 2022-05-25 14:47:48.786822+00 2022-05-25 14:47:48.786878+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0 tgLl0GNyDz0o {} \N Ashwin P/2022/04/R/4 f \N \N -59 ashwin.t@fyle.in Bus \N \N txY4DCpTgYr7 E/2022/04/T/2 C/2022/04/R/2 1 INR \N \N setczTjLjPcEG t f PAYMENT_PROCESSING \N Cost Center \N rpFBzjafvt8e 2022-04-06 06:30:00+00 2022-04-06 11:26:56.447+00 2022-04-06 11:26:21.669429+00 2022-05-17 07:17:48.799732+00 2022-05-25 14:47:48.830101+00 2022-05-25 14:47:48.830137+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0 tgXo6pS77uj9 {} \N Ashwin P/2022/04/R/2 f \N \N -60 ashwin.t@fyle.in Bus \N \N txLAP0oIB5Yb E/2022/05/T/3 C/2022/05/R/3 5 INR \N \N setcPVUmO6N5a t f PAYMENT_PROCESSING \N Cost Center \N rpOzmoYezM4y 2022-05-25 06:30:00+00 2022-05-25 14:48:47.895+00 2022-05-25 14:48:18.721551+00 2022-05-25 14:49:27.449793+00 2022-05-25 14:49:38.6847+00 2022-05-25 14:49:38.684734+00 PERSONAL {"Place": "", "Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/05/R/3 f \N \N +COPY public.expenses (id, employee_email, category, sub_category, project, expense_id, expense_number, claim_number, amount, currency, foreign_amount, foreign_currency, settlement_id, reimbursable, exported, state, vendor, cost_center, purpose, report_id, spent_at, approved_at, expense_created_at, expense_updated_at, created_at, updated_at, fund_source, custom_properties, verified_at, billable, paid_on_qbo, org_id, tax_amount, tax_group_id, file_ids, corporate_card_id, employee_name, payment_number, is_skipped, report_title, posted_at, accounting_export_summary, workspace_id, previous_export_state) FROM stdin; +1 ashwin.t@fyle.in Food \N Acera txZfwIJJn6pI E/2022/05/T/1 C/2022/05/R/1 5 INR \N \N setiYZAyfK7Ye t f PAYMENT_PROCESSING \N 01: San Francisco \N rpVBizZpEAos 2022-05-17 06:30:00+00 2022-05-17 15:52:10.498+00 2022-05-17 15:51:40.157566+00 2022-05-17 15:52:30.923957+00 2022-05-23 06:25:54.1468+00 2022-05-23 06:25:54.14685+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "", "Netsuite Sage Intacct Customer Qbo Department Hey": ""} \N \N f orOarwdPeIWs \N \N {} \N Joanna P/2022/05/R/1 f \N \N {} \N \N +2 ashwin.t@fyle.in Food \N Acera txRs11LMbHq7 E/2022/03/T/2 C/2022/03/R/2 999 INR \N \N setcWknPMAMeB t f PAYMENT_PROCESSING \N \N \N rp4odQDVFNBP 2022-03-09 06:30:00+00 2022-03-09 05:10:39.65+00 2022-03-09 05:09:55.106443+00 2022-05-17 07:16:31.559048+00 2022-05-23 06:25:54.18655+00 2022-05-23 06:25:54.186599+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "United Kingdom"} \N \N f orOarwdPeIWs 47.57 tg33IC8PoNNb {} \N Ashwin P/2022/03/R/2 f \N \N {} \N \N +3 ashwin.t@fyle.in Taxi \N Acera txNj2N2NRJPw E/2022/03/T/1 C/2022/03/R/1 2000 INR \N \N setUXr52IorxG t f PAYMENT_PROCESSING Uber \N \N rpBKhrCoEt0D 2022-03-08 06:30:00+00 2022-03-08 17:20:27.527+00 2022-03-08 17:19:09.169854+00 2022-05-17 07:16:29.41957+00 2022-05-23 06:25:54.2088+00 2022-05-23 06:25:54.208855+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "Bir Billing"} \N \N f orOarwdPeIWs 95.24 tg33IC8PoNNb {} \N Ashwin P/2022/03/R/1 f \N \N {} \N \N +4 ashwin.t@fyle.in Food \N Aaron Abbott txj8kWkDTyog E/2022/05/T/17 C/2022/05/R/5 1 USD \N \N set9k3fC23ByK f f PAYMENT_PROCESSING Ashwin Marketing \N rpH0YbevEADk 2022-05-17 17:00:00+00 2022-05-17 12:40:21.481+00 2022-05-17 12:37:46.724529+00 2022-05-17 12:43:32.838356+00 2022-05-23 11:11:27.90706+00 2022-05-23 11:11:27.907251+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 0.22 tggu76WXIdjY {} \N Joanna P/2022/05/R/8 f \N \N {} \N \N +5 user9@fyleforgotham.in Food \N Project 1 txHmoggWDQZs E/2021/04/T/310 C/2021/04/R/42 801 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N SME \N rp0jIc6urRHS 2020-05-10 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:37.727141+00 2022-05-17 07:17:23.922707+00 2022-05-23 11:11:27.923435+00 2022-05-23 11:11:27.923467+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiO3nQ5ZxnjJ} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +6 user8@fyleforgotham.in Office Supplies \N Project 9 txjFtOcEBO4C E/2021/04/T/277 C/2021/04/R/38 383 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Sales and Cross \N rpXV3IIN7LMJ 2020-05-06 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:23.409596+00 2022-05-17 07:17:23.922707+00 2022-05-23 11:11:27.940168+00 2022-05-23 11:11:27.940202+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiMCOjeTtZJn} \N Jessica Lane P/2022/05/R/3 f \N \N {} \N \N +7 user9@fyleforgotham.in Office Party \N Project 8 txU2qpKmrUR9 E/2021/04/T/312 C/2021/04/R/42 1188 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Strategy Planning \N rp0jIc6urRHS 2020-05-13 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:41.896287+00 2022-05-17 07:17:23.922707+00 2022-05-23 11:11:27.955311+00 2022-05-23 11:11:27.955345+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fik0BjLHFYpc} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +8 user9@fyleforgotham.in Office Party \N Project 4 tx3o8Yb29JVH E/2021/04/T/314 C/2021/04/R/42 701 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Retail \N rp0jIc6urRHS 2020-05-05 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:46.065774+00 2022-05-17 07:17:16.050882+00 2022-05-23 11:11:27.972451+00 2022-05-23 11:11:27.972485+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiBqn8QEjQoX} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +9 ashwin.t@fyle.in Food \N Aaron Abbott txNEnr2OEtkm E/2021/12/T/36 C/2021/12/R/36 18.46 USD 1371 INR setGtUuWv5015 f f PAYMENT_PROCESSING Shiv Sagar Marketing \N rpjdzTobT8VT 2021-09-30 17:00:00+00 2021-12-29 15:15:49.162+00 2021-12-29 15:15:26.659581+00 2022-05-17 07:16:58.388588+00 2022-05-23 11:11:27.989462+00 2022-05-23 11:11:27.989498+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 4.04 \N {fi9pLK99HpFq} \N Ashwin P/2022/05/R/6 f \N \N {} \N \N +10 user8@fyleforgotham.in Office Party \N Project 7 tx77CqtMUW2L E/2021/04/T/282 C/2021/04/R/38 797 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Marketing \N rpXV3IIN7LMJ 2020-05-01 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:33.736778+00 2022-05-17 07:16:54.275934+00 2022-05-23 11:11:28.004384+00 2022-05-23 11:11:28.004418+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiG8Ra8UGHNF} \N Jessica Lane P/2022/05/R/3 f \N \N {} \N \N +11 user9@fyleforgotham.in Office Supplies \N Project 9 txJFfGeWSQdD E/2021/04/T/308 C/2021/04/R/42 608 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Sales and Cross \N rp0jIc6urRHS 2020-05-03 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:33.627277+00 2022-05-17 07:16:54.275934+00 2022-05-23 11:11:28.017447+00 2022-05-23 11:11:28.017479+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiF1M3jRGeMq} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +12 user9@fyleforgotham.in Office Party \N Project 1 txqy5WraeWt6 E/2021/04/T/309 C/2021/04/R/42 1803 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Retail \N rp0jIc6urRHS 2020-05-01 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:35.669916+00 2022-05-17 07:16:46.248509+00 2022-05-23 11:11:28.034208+00 2022-05-23 11:11:28.034243+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiGqUizprTME} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +13 user9@fyleforgotham.in Food \N Project 4 txUve21xzdtr E/2021/04/T/311 C/2021/04/R/42 1104 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Legal and Secretarial \N rp0jIc6urRHS 2020-05-11 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:39.719515+00 2022-05-17 07:16:46.248509+00 2022-05-23 11:11:28.048945+00 2022-05-23 11:11:28.048978+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fizeHWCFAS9Y} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +14 user9@fyleforgotham.in Food \N Project 4 tx4q4RJTeYBw E/2021/04/T/313 C/2021/04/R/42 642 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Internal \N rp0jIc6urRHS 2020-05-07 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:43.931468+00 2022-05-17 07:16:45.527217+00 2022-05-23 11:11:28.065481+00 2022-05-23 11:11:28.06552+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fioo7Iifocss} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +15 user8@fyleforgotham.in Software \N Project 9 txYgJFgb8MET E/2021/04/T/281 C/2021/04/R/38 1007 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Sales and Cross \N rpXV3IIN7LMJ 2020-05-12 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:31.69043+00 2022-05-17 07:16:36.602763+00 2022-05-23 11:11:28.084189+00 2022-05-23 11:11:28.084227+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fi1ftfZiR2yH} \N Jessica Lane P/2022/05/R/3 f \N \N {} \N \N +16 user8@fyleforgotham.in Food \N Project 10 txgryOAgCf9b E/2021/04/T/283 C/2021/04/R/38 727 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Marketing \N rpXV3IIN7LMJ 2020-05-03 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:36.010173+00 2022-05-17 07:16:35.584167+00 2022-05-23 11:11:28.10159+00 2022-05-23 11:11:28.101782+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiDrNR9MnsnG} \N Jessica Lane P/2022/05/R/3 f \N \N {} \N \N +17 user9@fyleforgotham.in Others \N Project 1 tx5PXU8lacAv E/2021/04/T/307 C/2021/04/R/42 592 USD \N \N sett283OqFZ42 t f PAYMENT_PROCESSING \N Retail \N rp0jIc6urRHS 2020-05-01 10:00:00+00 2022-05-09 19:24:16.684+00 2021-04-09 11:35:31.337996+00 2022-05-17 07:15:15.733851+00 2022-05-23 11:11:28.121484+00 2022-05-23 11:11:28.121649+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiS51iv21cIP} \N Justin Glass P/2022/05/R/4 f \N \N {} \N \N +18 user8@fyleforgotham.in Food \N Project 10 txgXWggJGBf0 E/2021/04/T/279 C/2021/04/R/38 1390 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Strategy Planning \N rpXV3IIN7LMJ 2020-05-10 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:27.541116+00 2022-05-17 07:15:15.733851+00 2022-05-23 11:11:28.135911+00 2022-05-23 11:11:28.135993+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fi3JOklBstEc} \N Jessica Lane P/2022/05/R/3 f \N \N {} \N \N +19 user8@fyleforgotham.in Office Party \N Project 6 txladt6mN4nj E/2021/04/T/280 C/2021/04/R/38 -352 USD \N \N setr9WSZQIwzH t f PAYMENT_PROCESSING \N Retail \N rpXV3IIN7LMJ 2020-05-07 10:00:00+00 2022-05-09 19:24:04.662+00 2021-04-09 11:34:29.655549+00 2022-05-17 07:15:15.733851+00 2022-05-23 11:11:28.150877+00 2022-05-23 11:11:28.150914+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiS2JucojE2n} \N Jessica Lane P/2022/05/R/3 f \N \N {} \N \N +20 user4@fyleforgotham.in Software \N Project 6 txC5usIbRgRg E/2021/04/T/167 C/2021/04/R/23 673 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N Sales and Cross \N rp0l2lFIQ0Uk 2020-05-10 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:12.5464+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.165178+00 2022-05-23 11:11:28.165211+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiF0RDE9b8Or} \N Samantha Washington P/2022/05/R/5 f \N \N {} \N \N +21 user4@fyleforgotham.in Software \N Project 4 txh7HByDjFXT E/2021/04/T/165 C/2021/04/R/23 608 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N Audit \N rp0l2lFIQ0Uk 2020-05-07 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:08.445843+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.179708+00 2022-05-23 11:11:28.17974+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiDcXmeSsyvA} \N Samantha Washington P/2022/05/R/5 f \N \N {} \N \N +22 user4@fyleforgotham.in Software \N Project 6 txk9w63xZYyA E/2021/04/T/168 C/2021/04/R/23 900 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N Strategy Planning \N rp0l2lFIQ0Uk 2020-05-04 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:14.725066+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.195923+00 2022-05-23 11:11:28.195955+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fiEFya0Ia9bI} \N Samantha Washington P/2022/05/R/5 f \N \N {} \N \N +23 user4@fyleforgotham.in Office Party \N Project 7 txRrLLI8zD4k E/2021/04/T/166 C/2021/04/R/23 922 USD \N \N set3ScziYvftR t f PAYMENT_PROCESSING \N SME \N rp0l2lFIQ0Uk 2020-05-14 10:00:00+00 2021-04-09 11:30:45.396+00 2021-04-09 11:30:10.448385+00 2022-05-17 07:15:14.840893+00 2022-05-23 11:11:28.210457+00 2022-05-23 11:11:28.210501+00 PERSONAL {} \N \N f or79Cob97KSh \N \N {fitQHS1kdC25} \N Samantha Washington P/2022/05/R/5 f \N \N {} \N \N +24 ashwin.t@fyle.in Taxi \N Aaron Abbott txgUAIXUPQ8r E/2022/05/T/15 C/2022/05/R/4 100 USD \N \N setDiksMn83K7 f f PAYMENT_PROCESSING Allison Hill Marketing \N rpViBmuYmAgw 2022-05-13 17:00:00+00 2022-05-13 09:30:13.484+00 2022-05-13 09:29:19.925312+00 2022-05-13 09:32:06.643941+00 2022-05-23 11:11:28.227764+00 2022-05-23 11:11:28.227804+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh \N \N {} \N Joanna P/2022/05/R/7 f \N \N {} \N \N +25 ashwin.t@fyle.in Food \N Aaron Abbott txxTi9ZfdepC E/2022/05/T/16 C/2022/05/R/4 50 USD \N \N setDiksMn83K7 t f PAYMENT_PROCESSING Ashwin Marketing \N rpViBmuYmAgw 2022-05-13 17:00:00+00 2022-05-13 09:30:13.484+00 2022-05-13 09:29:43.535468+00 2022-05-13 09:32:06.643941+00 2022-05-23 11:11:28.241406+00 2022-05-23 11:11:28.24144+00 PERSONAL {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh \N \N {} \N Joanna P/2022/05/R/7 f \N \N {} \N \N +26 sravan.kumar@fyle.in WIP \N Bebe Rexha txGTyHWrmv7s E/2022/05/T/8 C/2022/05/R/9 10 USD \N \N setdKjMoHS47d t f PAYMENT_PROCESSING \N Adidas \N rpDA8I6af7K2 2022-05-23 17:00:00+00 2022-05-23 04:29:46.308+00 2022-05-23 04:28:41.961851+00 2022-05-23 04:29:59.754485+00 2022-05-23 11:35:09.675594+00 2022-05-23 11:35:09.675634+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fieQIOTwDiXj} \N sravan k P/2022/05/R/7 f \N \N {} \N \N +27 sravan.kumar@fyle.in WIP \N Bebe Rexha txj6oP5vEevy E/2022/05/T/9 C/2022/05/R/9 77 USD \N \N setdKjMoHS47d f f PAYMENT_PROCESSING \N Adidas \N rpDA8I6af7K2 2022-05-23 17:00:00+00 2022-05-23 04:29:46.308+00 2022-05-23 04:28:55.139059+00 2022-05-23 04:29:59.754485+00 2022-05-23 11:35:09.69751+00 2022-05-23 11:35:09.697545+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fiUVyx8HQ30G} \N sravan k P/2022/05/R/7 f \N \N {} \N \N +28 sravan.kumar@fyle.in WIP \N Bebe Rexha tx3i1mrGprDs E/2022/05/T/7 C/2022/05/R/8 1 USD \N \N set2S6CqU59C1 f f PAYMENT_PROCESSING \N Adidas \N rp1dtMICkdqU 2022-05-23 17:00:00+00 2022-05-23 04:22:33.747+00 2022-05-23 04:22:19.588463+00 2022-05-23 04:23:50.476021+00 2022-05-23 11:35:09.718808+00 2022-05-23 11:35:09.718846+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fiLO4HUsnbik} \N sravan k P/2022/05/R/6 f \N \N {} \N \N +29 sravan.kumar@fyle.in Courier \N Bebe Rexha txglJd1GPCXm E/2022/04/T/182 C/2022/05/R/2 1 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpk22gODSyaY 2022-04-26 17:00:00+00 2022-05-20 13:35:18.576+00 2022-04-26 11:59:43.384164+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.733256+00 2022-05-23 11:35:09.733313+00 PERSONAL {"Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fi12kYip1LCk} \N sravan k P/2022/05/R/5 f \N \N {} \N \N +30 sravan.kumar@fyle.in WIP \N Bebe Rexha txh3QPqFZE47 E/2022/04/T/183 C/2022/04/R/34 1 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpVjuHvS0wx5 2022-04-27 17:00:00+00 2022-04-27 05:28:24.659+00 2022-04-27 05:16:57.805222+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.749358+00 2022-05-23 11:35:09.749437+00 PERSONAL {"Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/5 f \N \N {} \N \N +31 sravan.kumar@fyle.in WIP \N Bebe Rexha txJThyugq3dO E/2022/05/T/4 C/2022/05/R/4 20 USD \N \N setcAs1ng0qTT f f PAYMENT_PROCESSING \N Adidas \N rpOuhx4SIOD8 2022-05-21 17:00:00+00 2022-05-21 03:33:05.796+00 2022-05-21 03:32:51.691013+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.768353+00 2022-05-23 11:35:09.768393+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/5 f \N \N {} \N \N +32 sravan.kumar@fyle.in WIP \N Bebe Rexha txNGSSLoRUiq E/2022/05/T/2 C/2022/05/R/3 1 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpDdPtQu8Leg 2021-08-07 17:00:00+00 2022-05-20 13:35:45.95+00 2022-05-20 13:34:43.935588+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.787782+00 2022-05-23 11:35:09.787815+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/5 f \N \N {} \N \N +33 sravan.kumar@fyle.in WIP \N Bebe Rexha txW6r1wTHCQr E/2022/05/T/6 C/2022/05/R/6 96 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpawE81idoYo 2022-05-23 17:00:00+00 2022-05-23 03:50:32.697+00 2022-05-23 03:47:39.0044+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.807184+00 2022-05-23 11:35:09.807218+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fiwZAHxieaWd} \N sravan k P/2022/05/R/5 f \N \N {} \N \N +34 sravan.kumar@fyle.in WIP \N Bebe Rexha txXy5Prbt9iH E/2022/05/T/1 C/2022/05/R/1 10 USD \N \N setcAs1ng0qTT t f PAYMENT_PROCESSING \N Adidas \N rpmabEGhBlGo 2022-05-02 17:00:00+00 2022-05-02 11:47:18.267+00 2022-05-02 11:42:40.798011+00 2022-05-23 03:50:48.028261+00 2022-05-23 11:35:09.825094+00 2022-05-23 11:35:09.825131+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {fi6LddX3MBgw} \N sravan k P/2022/05/R/5 f \N \N {} \N \N +35 ashwin.t@fyle.in Food \N \N txeSMU6QuRpW E/2022/05/T/10 C/2022/05/R/10 1 USD \N \N setEwmx4RfC56 t f PAYMENT_PROCESSING \N \N \N rpm6D48AcjKK 2022-05-23 17:00:00+00 2022-05-23 11:38:40.875+00 2022-05-23 11:38:18.295764+00 2022-05-23 11:39:08.507389+00 2022-05-23 12:31:23.914452+00 2022-05-23 12:31:23.914502+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N Joanna P/2022/05/R/8 f \N \N {} \N \N +36 ashwin.t@fyle.in Flight \N Aaron Abbott txW7qE5DUF82 E/2022/05/T/20 C/2022/05/R/6 5 USD \N \N setCb41PcrHmO t f PAYMENT_PROCESSING Ashwin \N \N rpYkgyHrGcg8 2022-05-23 17:00:00+00 2022-05-23 13:34:56.716+00 2022-05-23 13:32:45.769742+00 2022-05-23 13:37:01.524157+00 2022-05-23 13:37:17.821638+00 2022-05-23 13:37:17.821685+00 PERSONAL {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 1.09 tggu76WXIdjY {} \N Joanna P/2022/05/R/9 f \N \N {} \N \N +37 ashwin.t@fyle.in Flight \N Aaron Abbott tx4w7YXZV1fa E/2022/05/T/21 C/2022/05/R/7 11 USD \N \N setlcYd0kfoBv f f PAYMENT_PROCESSING Ashwin Marketing \N rp5uAOkQovrD 2022-05-23 17:00:00+00 2022-05-23 17:31:49.913+00 2022-05-23 17:29:33.605913+00 2022-05-23 17:32:19.370016+00 2022-05-23 17:35:13.712115+00 2022-05-23 17:35:13.712159+00 CCC {"Team": "", "Class": "", "Klass": "", "Location": "", "Team Copy": "", "Tax Groups": "", "Departments": "", "Team 2 Postman": "", "User Dimension": "", "Location Entity": "", "Operating System": "", "System Operating": "", "User Dimension Copy": "", "Custom Expense Field": null} \N \N f or79Cob97KSh 2.41 tggu76WXIdjY {fihTwZoMuRVN} \N Joanna P/2022/05/R/10 f \N \N {} \N \N +38 ashwin.t@fyle.in Food \N \N txP7sCqGUYnA E/2022/05/T/3 C/2022/05/R/3 41 INR \N \N setLD0YF7n0QG t f PAYMENT_PROCESSING \N 01: San Francisco \N rp5R0j7JNtVu 2022-05-23 06:30:00+00 2022-05-23 11:07:18.467+00 2022-05-23 11:06:58.305532+00 2022-05-23 11:08:33.781939+00 2022-05-25 14:36:48.708012+00 2022-05-25 14:36:48.708056+00 PERSONAL {"Jilkiplk": "", "Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "", "Netsuite Sage Intacct Customer Qbo Department Hey": ""} \N f f orOarwdPeIWs \N \N {fiWcpsmmgnac} \N Joanna P/2022/05/R/3 f \N \N {} \N \N +39 ashwin.t@fyle.in Food \N \N txpGmLpiUhNk E/2022/05/T/2 C/2022/05/R/2 23 INR \N \N set8xNhjXpvTC t f PAYMENT_PROCESSING \N 01: San Francisco \N rpF3G59kmLmX 2022-05-23 06:30:00+00 2022-05-23 10:42:16.038+00 2022-05-23 10:42:02.422005+00 2022-05-23 10:42:31.331925+00 2022-05-25 14:36:48.747386+00 2022-05-25 14:36:48.747422+00 PERSONAL {"Ns Deppp": "", "This Is 1": "", "This Is 2": "", "This Is 3 Haha": "", "Wow This Is Wor": "", "Netsuite Sage Intacct Customer Qbo Department Hey": ""} \N f f orOarwdPeIWs \N \N {fij5zhC5i0Ny} \N Joanna P/2022/05/R/2 f \N \N {} \N \N +40 sravan.kumar@fyle.in Food \N \N txjIqTCtkkC8 E/2022/05/T/21 C/2022/05/R/18 100 USD \N \N set3ZMFXrDPL3 f f PAYMENT_PROCESSING \N \N \N rpLawO11bFib 2022-05-25 17:00:00+00 2022-05-25 08:59:25.649+00 2022-05-25 08:59:07.718891+00 2022-05-25 09:04:05.66983+00 2022-05-25 14:38:34.641061+00 2022-05-25 14:38:34.641096+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N f f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/16 f \N \N {} \N \N +41 sravan.kumar@fyle.in WIP \N Bebe Rexha txUPRc3VwxOP E/2022/05/T/19 C/2022/05/R/17 101 USD \N \N setb1pSLMIok8 f f PAYMENT_PROCESSING \N Adidas \N rpv1txzAsgr3 2021-01-01 17:00:00+00 2022-05-25 07:24:12.987+00 2022-05-25 07:21:40.598113+00 2022-05-25 07:25:00.848892+00 2022-05-25 14:38:34.653781+00 2022-05-25 14:38:34.653817+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/15 f \N \N {} \N \N +42 ashwin.t@fyle.in Food \N \N txUDvDmEV4ep E/2022/05/T/18 C/2022/05/R/16 5 USD \N \N set33iAVXO7BA t f PAYMENT_PROCESSING \N \N \N rpE2JyATZhDe 2020-05-25 17:00:00+00 2022-05-25 06:05:23.362+00 2022-05-25 06:04:46.557927+00 2022-05-25 06:05:47.36985+00 2022-05-25 14:38:34.664871+00 2022-05-25 14:38:34.664906+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N Joanna P/2022/05/R/14 f \N \N {} \N \N +43 sravan.kumar@fyle.in WIP \N Bebe Rexha tx1FW3uxYZG6 E/2022/05/T/16 C/2022/05/R/15 151 USD \N \N setzFn3FK5t80 f f PAYMENT_PROCESSING \N Adidas \N rprwGgzOZyfR 2022-05-25 17:00:00+00 2022-05-25 03:41:49.042+00 2022-05-25 03:41:28.839711+00 2022-05-25 03:42:10.145663+00 2022-05-25 14:38:34.680226+00 2022-05-25 14:38:34.680352+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/13 f \N \N {} \N \N +44 sravan.kumar@fyle.in WIP \N Bebe Rexha txVXhyVB8mgK E/2022/05/T/15 C/2022/05/R/14 45 USD \N \N setsN8cLD9KIn f f PAYMENT_PROCESSING \N Adidas \N rpnG3lZYDsHU 2022-05-25 17:00:00+00 2022-05-25 02:48:53.791+00 2022-05-25 02:48:37.432989+00 2022-05-25 02:49:18.189037+00 2022-05-25 14:38:34.700082+00 2022-05-25 14:38:34.700127+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/12 f \N \N {} \N \N +45 sravan.kumar@fyle.in WIP \N Bebe Rexha txBMQRkBQciI E/2022/05/T/14 C/2022/05/R/13 10 USD \N \N setanDKqMZfXB f f PAYMENT_PROCESSING \N Adidas \N rpVvNQvE2wbm 2022-05-25 17:00:00+00 2022-05-25 02:38:40.858+00 2022-05-25 02:38:25.832419+00 2022-05-25 02:39:08.208877+00 2022-05-25 14:38:34.713384+00 2022-05-25 14:38:34.713419+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/11 f \N \N {} \N \N +46 sravan.kumar@fyle.in WIP \N Bebe Rexha txkw3dt3umkN E/2022/05/T/12 C/2022/05/R/12 101 USD \N \N setBe6qAlNXPU f f PAYMENT_PROCESSING \N Adidas \N rp5lITpxFLxE 2022-05-24 17:00:00+00 2022-05-24 15:59:13.26+00 2022-05-24 15:55:50.369024+00 2022-05-24 16:00:27.982+00 2022-05-25 14:38:34.728474+00 2022-05-25 14:38:34.728523+00 CCC {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/10 f \N \N {} \N \N +47 sravan.kumar@fyle.in WIP \N Bebe Rexha tx75COnDBjXm E/2022/05/T/11 C/2022/05/R/11 65 USD \N \N setHwMulHK6X3 t f PAID \N Adidas \N rp3YxnytLrgS 2021-01-11 17:00:00+00 2022-05-24 13:54:57.723+00 2022-05-24 13:53:31.773336+00 2022-05-24 15:54:56.794866+00 2022-05-25 14:38:34.744673+00 2022-05-25 14:38:34.744709+00 PERSONAL {"Card": "", "Killua": "", "Classes": "", "avc_123": null, "New Field": "", "Multi field": "", "Testing This": "", "abc in [123]": null, "POSTMAN FIELD": "", "Netsuite Class": ""} \N \N f orPJvXuoLqvJ \N \N {} \N sravan k P/2022/05/R/9 f \N \N {} \N \N +48 ashwin.t@fyle.in Bus \N \N txGtk31AfEY5 E/2022/04/T/16 C/2022/04/R/15 1 INR \N \N setbb8jxonU9n t f PAYMENT_PROCESSING \N \N \N rplJh22whCmG 2022-04-08 06:30:00+00 2022-04-08 09:24:49.042+00 2022-04-08 09:24:22.738403+00 2022-05-24 07:14:36.820174+00 2022-05-25 14:47:48.349048+00 2022-05-25 14:47:48.349141+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/04/R/14 f \N \N {} \N \N +49 ashwin.t@fyle.in Bus \N \N txhXSn5F9uZ4 E/2022/04/T/12 C/2022/04/R/11 1 INR \N \N setHIFl6CHqi7 t f PAYMENT_PROCESSING \N \N \N rpZAGw9was22 2022-04-07 06:30:00+00 2022-04-07 11:15:46.665+00 2022-04-07 11:15:26.848334+00 2022-05-24 07:14:28.930784+00 2022-05-25 14:47:48.382086+00 2022-05-25 14:47:48.382123+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/04/R/10 f \N \N {} \N \N +50 ashwin.t@fyle.in Bus \N \N txJGT0u4l47D E/2022/04/T/11 C/2022/04/R/10 22 INR \N \N setIfAz8a7Uqs t f PAYMENT_PROCESSING \N Cost Center \N rp3AJwfsGBEZ 2022-04-07 06:30:00+00 2022-04-07 11:13:19.68+00 2022-04-07 11:12:56.790163+00 2022-05-24 07:14:28.68168+00 2022-05-25 14:47:48.40861+00 2022-05-25 14:47:48.408658+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/04/R/9 f \N \N {} \N \N +51 ashwin.t@fyle.in Bus \N \N txu2bPK9QtSP E/2022/04/T/7 C/2022/04/R/6 -11 INR \N \N setctmftpoQTr t f PAYMENT_PROCESSING \N Cost Center \N rpeNhwlQn2fb 2022-04-06 06:30:00+00 2022-04-06 17:10:15.355+00 2022-04-06 17:09:27.222098+00 2022-05-17 07:18:10.821355+00 2022-05-25 14:47:48.432631+00 2022-05-25 14:47:48.432666+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Ashwin P/2022/04/R/6 f \N \N {} \N \N +52 ashwin.t@fyle.in Bus \N \N txjjSTAhBXsw E/2022/04/T/1 C/2022/04/R/1 1 INR \N \N setoxDCmYrJvK t f PAYMENT_PROCESSING \N Cost Center \N rpP6feukA57q 2022-04-06 06:30:00+00 2022-04-06 11:15:43.619+00 2022-04-06 11:15:06.419141+00 2022-05-17 07:18:05.368491+00 2022-05-25 14:47:48.468033+00 2022-05-25 14:47:48.46831+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0.04 tg1MoZ63tdCg {} \N Ashwin P/2022/04/R/1 f \N \N {} \N \N +53 ashwin.t@fyle.in Bus \N \N txhJmYEqGRT8 E/2022/04/T/13 C/2022/04/R/12 200 INR \N \N setulC9Vi6zXD t f PAYMENT_PROCESSING \N Cost Center \N rpOiZLTpX4OI 2022-04-08 06:30:00+00 2022-04-08 05:29:38.258+00 2022-04-08 05:28:59.489277+00 2022-05-17 07:17:55.683792+00 2022-05-25 14:47:48.574082+00 2022-05-25 14:47:48.574132+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 21.43 tg4OD3Ig57FK {} \N Ashwin P/2022/04/R/11 f \N \N {} \N \N +54 ashwin.t@fyle.in Bus \N \N txtlNGpZAL8n E/2022/04/T/15 C/2022/04/R/14 200 INR \N \N setOgaLUS52QZ t f PAYMENT_PROCESSING \N \N \N rp6aB6XQhMPb 2022-04-08 06:30:00+00 2022-04-08 08:06:31.754+00 2022-04-08 08:05:52.171087+00 2022-05-17 07:17:55.683792+00 2022-05-25 14:47:48.6507+00 2022-05-25 14:47:48.650737+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Ashwin P/2022/04/R/13 f \N \N {} \N \N +55 ashwin.t@fyle.in Bus \N \N txPsADTUzf2I E/2022/04/T/9 C/2022/04/R/8 1 INR \N \N setNZqYFdQib8 t f PAYMENT_PROCESSING \N Cost Center \N rpuoFNcLAKMc 2022-04-07 06:30:00+00 2022-04-07 09:15:05.368+00 2022-04-07 09:14:28.50854+00 2022-05-17 07:17:54.45687+00 2022-05-25 14:47:48.680265+00 2022-05-25 14:47:48.680359+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0.11 tg4OD3Ig57FK {} \N Ashwin P/2022/04/R/8 f \N \N {} \N \N +56 ashwin.t@fyle.in Bus \N \N tx9FusYWBQMW E/2022/04/T/5 C/2022/04/R/5 1 INR \N \N settQTSdGcOKI t f PAYMENT_PROCESSING \N \N \N rpZxs9qFrWic 2022-04-06 06:30:00+00 2022-04-06 11:51:58.671+00 2022-04-06 11:51:19.394765+00 2022-05-17 07:17:49.188761+00 2022-05-25 14:47:48.722995+00 2022-05-25 14:47:48.723131+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0.11 tg4OD3Ig57FK {} \N Ashwin P/2022/04/R/5 f \N \N {} \N \N +57 ashwin.t@fyle.in Bus \N \N tx0SDpb0Tkgw E/2022/04/T/3 C/2022/04/R/3 1 INR \N \N setZ1yiEV5aaH t f PAYMENT_PROCESSING \N Cost Center \N rpHKjTkMnXH3 2022-04-06 06:30:00+00 2022-04-06 11:36:27.241+00 2022-04-06 11:35:57.496504+00 2022-05-17 07:17:49.041897+00 2022-05-25 14:47:48.74943+00 2022-05-25 14:47:48.749466+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Ashwin P/2022/04/R/3 f \N \N {} \N \N +58 ashwin.t@fyle.in Bus \N \N txRJEOyCAW6i E/2022/04/T/4 C/2022/04/R/4 1 INR \N \N set4OQIR1sSeA t f PAYMENT_PROCESSING \N Cost Center \N rpXWxpSaeyEE 2022-04-06 06:30:00+00 2022-04-06 11:43:31.356+00 2022-04-06 11:42:39.66612+00 2022-05-17 07:17:49.041897+00 2022-05-25 14:47:48.786822+00 2022-05-25 14:47:48.786878+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0 tgLl0GNyDz0o {} \N Ashwin P/2022/04/R/4 f \N \N {} \N \N +59 ashwin.t@fyle.in Bus \N \N txY4DCpTgYr7 E/2022/04/T/2 C/2022/04/R/2 1 INR \N \N setczTjLjPcEG t f PAYMENT_PROCESSING \N Cost Center \N rpFBzjafvt8e 2022-04-06 06:30:00+00 2022-04-06 11:26:56.447+00 2022-04-06 11:26:21.669429+00 2022-05-17 07:17:48.799732+00 2022-05-25 14:47:48.830101+00 2022-05-25 14:47:48.830137+00 PERSONAL {"Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy 0 tgXo6pS77uj9 {} \N Ashwin P/2022/04/R/2 f \N \N {} \N \N +60 ashwin.t@fyle.in Bus \N \N txLAP0oIB5Yb E/2022/05/T/3 C/2022/05/R/3 5 INR \N \N setcPVUmO6N5a t f PAYMENT_PROCESSING \N Cost Center \N rpOzmoYezM4y 2022-05-25 06:30:00+00 2022-05-25 14:48:47.895+00 2022-05-25 14:48:18.721551+00 2022-05-25 14:49:27.449793+00 2022-05-25 14:49:38.6847+00 2022-05-25 14:49:38.684734+00 PERSONAL {"Place": "", "Kaneki": "", "select": ""} \N \N f orZu2yrz7zdy \N \N {} \N Joanna P/2022/05/R/3 f \N \N {} \N \N \. @@ -33818,7 +33824,7 @@ SELECT pg_catalog.setval('public.django_content_type_id_seq', 45, true); -- Name: django_migrations_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres -- -SELECT pg_catalog.setval('public.django_migrations_id_seq', 168, true); +SELECT pg_catalog.setval('public.django_migrations_id_seq', 171, true); -- @@ -34891,6 +34897,13 @@ CREATE INDEX expense_fields_workspace_id_b60af18c ON public.expense_fields USING CREATE INDEX expense_filters_workspace_id_0ecd4914 ON public.expense_filters USING btree (workspace_id); +-- +-- Name: expenses_workspace_id_72fb819f; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX expenses_workspace_id_72fb819f ON public.expenses USING btree (workspace_id); + + -- -- Name: fyle_accounting_mappings_d_workspace_id_a6a3ab6a; Type: INDEX; Schema: public; Owner: postgres -- @@ -35270,6 +35283,14 @@ ALTER TABLE ONLY public.expense_group_settings ADD CONSTRAINT expense_group_settings_workspace_id_4c110bbe_fk_workspaces_id FOREIGN KEY (workspace_id) REFERENCES public.workspaces(id) DEFERRABLE INITIALLY DEFERRED; +-- +-- Name: expenses expenses_workspace_id_72fb819f_fk_workspaces_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.expenses + ADD CONSTRAINT expenses_workspace_id_72fb819f_fk_workspaces_id FOREIGN KEY (workspace_id) REFERENCES public.workspaces(id) DEFERRABLE INITIALLY DEFERRED; + + -- -- Name: mappings fyle_accounting_mapp_destination_id_79497f6e_fk_fyle_acco; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- diff --git a/tests/test_fyle/fixtures.py b/tests/test_fyle/fixtures.py index 872fc142..e640c20a 100644 --- a/tests/test_fyle/fixtures.py +++ b/tests/test_fyle/fixtures.py @@ -1,4 +1,129 @@ data = { + "expenses_spent_at":[ + { + 'id': '1234', + 'employee_email': 'jhonsnow@fyle.in', + 'category': 'Accounts Payable', + 'sub_category': 'Accounts Payable', + 'project': 'Aaron Abbott', + 'project_id': 263589, + 'expense_number': 'E/2021/12/T/3', + 'payment_number': 'P/2023/8/T/1221', + 'org_id': 'orPJvXuoLqvJ', + 'claim_number': 'C/2021/12/R/1', + 'employee_name': 'ashwin T', + 'amount': 150, + 'tax_amount': 0, + 'tax_group_id': None, + 'currency': 'USD', + 'foreign_amount': None, + 'foreign_currency': None, + 'report_title': 'report 2', + 'settlement_id': 'set3FdX0zRmdh', + 'reimbursable': True, + 'billable': False, + 'state': 'PAYMENT_PROCESSING', + 'vendor': None, + 'cost_center': None, + 'corporate_card_id': None, + 'purpose': None, + 'report_id': 'rpwhYusbrIjd', + 'file_ids': [], + 'spent_at': '2021-12-22T17:00:00', + 'approved_at': '2021-12-22T07:30:26.289842+00:00', + 'posted_at': None, + 'expense_created_at': '2021-12-23T07:14:13.990650+00:00', + 'expense_updated_at': '2021-12-27T05:26:43.954470+00:00', + 'source_account_type': 'PERSONAL_CASH_ACCOUNT', + 'verified_at': None, + 'custom_properties': { + 'Vehicle Type': '', + 'Fyle Categories': '', + }, + }, + { + 'id': '1235', + 'employee_email': 'jhonsnow@fyle.in', + 'category': 'Accounts Payable', + 'sub_category': 'Accounts Payable', + 'project': 'Aaron Abbott', + 'project_id': 263589, + 'expense_number': 'E/2021/12/T/2', + 'payment_number': 'P/2023/8/T/1221', + 'org_id': 'orPJvXuoLqvJ', + 'claim_number': 'C/2021/12/R/1', + 'amount': 99, + 'tax_amount': 0, + 'tax_group_id': None, + 'currency': 'USD', + 'foreign_amount': None, + 'foreign_currency': None, + 'report_title': 'report 2', + 'settlement_id': 'set3FdX0zRmdh', + 'employee_name': 'ashwin T', + 'reimbursable': True, + 'billable': False, + 'state': 'PAYMENT_PROCESSING', + 'vendor': None, + 'cost_center': None, + 'corporate_card_id': None, + 'purpose': None, + 'report_id': 'rpwhYusbrIjd', + 'file_ids': [], + 'spent_at': '2021-12-22T17:00:00', + 'approved_at': '2021-12-22T07:30:26.289842+00:00', + 'posted_at': None, + 'expense_created_at': '2021-12-22T07:30:26.289842+00:00', + 'expense_updated_at': '2021-12-23T07:11:39.770191+00:00', + 'source_account_type': 'PERSONAL_CASH_ACCOUNT', + 'verified_at': None, + 'custom_properties': { + 'Vehicle Type': '', + 'Fyle Categories': '', + }, + }, + { + 'id': '1236', + 'employee_email': 'jhonsnow@fyle.in', + 'category': 'Accounts Payable', + 'sub_category': 'Accounts Payable', + 'project': 'Aaron Abbott', + 'project_id': 263589, + 'expense_number': 'E/2021/12/T/1', + 'payment_number': 'P/2023/8/T/1221', + 'org_id': 'orPJvXuoLqvJ', + 'claim_number': 'C/2021/12/R/1', + 'amount': -99, + 'tax_amount': 0, + 'tax_group_id': None, + 'currency': 'USD', + 'foreign_amount': None, + 'foreign_currency': None, + 'report_title': 'report 2', + 'settlement_id': 'set3FdX0zRmdh', + 'employee_name': 'ashwin T', + 'reimbursable': True, + 'billable': False, + 'state': 'PAYMENT_PROCESSING', + 'vendor': None, + 'cost_center': None, + 'corporate_card_id': None, + 'purpose': None, + 'report_id': 'rpwhYusbrIjd', + 'file_ids': [], + 'spent_at': '2021-12-20T17:00:00', + 'approved_at': '2021-12-22T07:30:26.289842+00:00', + 'posted_at': None, + 'expense_created_at': '2021-12-22T07:30:26.289842+00:00', + 'expense_updated_at': '2021-12-23T07:11:39.770191+00:00', + 'source_account_type': 'PERSONAL_CASH_ACCOUNT', + 'verified_at': None, + 'custom_properties': { + 'Vehicle Type': '', + 'Fyle Categories': '', + }, + }, + ], "skipped_expenses": { 'count': 2, 'next': None, @@ -9123,11 +9248,24 @@ { 'code': '16200', 'created_at': '2022-08-12T16:50:53.658771+00:00', - 'display_name': 'Patents & Licenses', - 'id': 207983, + 'display_name': 'Patents & Licenses - Test', + 'id': 22330021, 'is_enabled': True, - 'name': 'Patents & Licenses', - 'org_id': 'orNoatdUnm1w', + 'name': 'Patents & Licenses - Included', + 'org_id': 'orPJvXuoLqvJ', + 'restricted_project_ids': None, + 'sub_category': None, + 'system_category': None, + 'updated_at': '2022-08-30T16:50:44.671684+00:00', + }, + { + 'code': '16200', + 'created_at': '2022-08-12T16:50:53.658771+00:00', + 'display_name': 'Patents & Licenses - Test', + 'id': 22330021, + 'is_enabled': True, + 'name': 'Patents & Licenses - Exempted', + 'org_id': 'orPJvXuoasfnn1J', 'restricted_project_ids': None, 'sub_category': None, 'system_category': None, diff --git a/tests/test_fyle/test_actions.py b/tests/test_fyle/test_actions.py new file mode 100644 index 00000000..6bfeab3c --- /dev/null +++ b/tests/test_fyle/test_actions.py @@ -0,0 +1,91 @@ +from django.conf import settings +from django.db.models import Q + +from apps.fyle.models import Expense, ExpenseGroup +from apps.fyle.actions import update_expenses_in_progress, mark_expenses_as_skipped, \ + mark_accounting_export_summary_as_synced, update_failed_expenses, update_complete_expenses +from apps.fyle.helpers import get_updated_accounting_export_summary +from apps.workspaces.models import Workspace + + +def test_update_expenses_in_progress(db): + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + update_expenses_in_progress(expenses) + + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + for expense in expenses: + assert expense.accounting_export_summary['synced'] == False + assert expense.accounting_export_summary['state'] == 'IN_PROGRESS' + assert expense.accounting_export_summary['url'] == '{}/workspaces/main/dashboard'.format( + settings.QBO_INTEGRATION_APP_URL + ) + assert expense.accounting_export_summary['error_type'] == None + assert expense.accounting_export_summary['id'] == expense.expense_id + + +def test_mark_expenses_as_skipped(db): + expense_group = ExpenseGroup.objects.filter(workspace_id=3).first() + expense_id = expense_group.expenses.first().id + expense_group.expenses.remove(expense_id) + + workspace = Workspace.objects.get(id=3) + mark_expenses_as_skipped(Q(), [expense_id], workspace) + + expense = Expense.objects.filter(id=expense_id).first() + + assert expense.is_skipped == True + assert expense.accounting_export_summary['synced'] == False + + +def test_mark_accounting_export_summary_as_synced(db): + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + for expense in expenses: + expense.accounting_export_summary = get_updated_accounting_export_summary( + 'tx_123', + 'SKIPPED', + None, + '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + True + ) + expense.save() + + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + mark_accounting_export_summary_as_synced(expenses) + + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + for expense in expenses: + assert expense.accounting_export_summary['synced'] == True + + +def test_update_failed_expenses(db): + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + update_failed_expenses(expenses, True) + + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + for expense in expenses: + assert expense.accounting_export_summary['synced'] == False + assert expense.accounting_export_summary['state'] == 'ERROR' + assert expense.accounting_export_summary['error_type'] == 'MAPPING' + assert expense.accounting_export_summary['url'] == '{}/workspaces/main/dashboard'.format( + settings.QBO_INTEGRATION_APP_URL + ) + assert expense.accounting_export_summary['id'] == expense.expense_id + + +def test_update_complete_expenses(db): + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + update_complete_expenses(expenses, 'https://qbo.google.com') + + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + for expense in expenses: + assert expense.accounting_export_summary['synced'] == False + assert expense.accounting_export_summary['state'] == 'COMPLETE' + assert expense.accounting_export_summary['error_type'] == None + assert expense.accounting_export_summary['url'] == 'https://qbo.google.com' + assert expense.accounting_export_summary['id'] == expense.expense_id diff --git a/tests/test_fyle/test_helpers.py b/tests/test_fyle/test_helpers.py index b083467a..b80ea95d 100644 --- a/tests/test_fyle/test_helpers.py +++ b/tests/test_fyle/test_helpers.py @@ -1,4 +1,5 @@ from asyncio.log import logger +from django.conf import settings import pytest from rest_framework.response import Response @@ -12,7 +13,9 @@ get_request, post_request, ) -from apps.fyle.models import ExpenseFilter +from apps.fyle.models import ExpenseFilter, Expense +from apps.fyle.helpers import get_updated_accounting_export_summary +from apps.fyle.actions import __bulk_update_expenses def test_post_request(mocker): @@ -340,3 +343,65 @@ def test_multiple_construct_expense_filter(): response = Q(**filter_2) | Q(**filter_3) assert final_filter == response + + +def test_get_updated_accounting_export_summary(): + updated_accounting_export_summary = get_updated_accounting_export_summary( + 'tx_123', + 'SKIPPED', + None, + '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + True + ) + expected_updated_accounting_export_summary = { + 'id': 'tx_123', + 'state': 'SKIPPED', + 'error_type': None, + 'url': '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + 'synced': True + } + + assert updated_accounting_export_summary == expected_updated_accounting_export_summary + + updated_accounting_export_summary = get_updated_accounting_export_summary( + 'tx_123', + 'SKIPPED', + None, + '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + False + ) + expected_updated_accounting_export_summary = { + 'id': 'tx_123', + 'state': 'SKIPPED', + 'error_type': None, + 'url': '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + 'synced': False + } + + assert updated_accounting_export_summary == expected_updated_accounting_export_summary + + +def test_bulk_update_expenses(db): + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + for expense in expenses: + expense.accounting_export_summary = get_updated_accounting_export_summary( + expense.expense_id, + 'SKIPPED', + None, + '{}/workspaces/main/export_log'.format(settings.QBO_INTEGRATION_APP_URL), + True + ) + expense.save() + + __bulk_update_expenses(expenses) + + expenses = Expense.objects.filter(org_id='or79Cob97KSh') + + for expense in expenses: + assert expense.accounting_export_summary['synced'] == True + assert expense.accounting_export_summary['state'] == 'SKIPPED' + assert expense.accounting_export_summary['error_type'] == None + assert expense.accounting_export_summary['url'] == '{}/workspaces/main/export_log'.format( + settings.QBO_INTEGRATION_APP_URL + ) + assert expense.accounting_export_summary['id'] == expense.expense_id diff --git a/tests/test_fyle/test_models.py b/tests/test_fyle/test_models.py index e7941acb..7aeff38b 100644 --- a/tests/test_fyle/test_models.py +++ b/tests/test_fyle/test_models.py @@ -5,6 +5,7 @@ ExpenseGroup, ExpenseGroupSettings, Reimbursement, + Workspace, WorkspaceGeneralSettings, _format_date, _group_expenses, @@ -65,6 +66,29 @@ def test_create_reimbursement(db): paid_reimbursement.state == 'PAID' +def test_create_expense_groups_by_report_id_fund_source_spent_at(db): + expenses = data['expenses_spent_at'] + + expense_objects = Expense.create_expense_objects(expenses, 1) + + workspace = Workspace.objects.get(id=1) + + expense_group_setting = ExpenseGroupSettings.objects.get(workspace_id=1) + expense_group_setting.reimbursable_export_date_type = 'spent_at' + reimbursable_expense_group_fields = expense_group_setting.reimbursable_expense_group_fields + reimbursable_expense_group_fields.append('spent_at') + expense_group_setting.reimbursable_expense_group_fields = reimbursable_expense_group_fields + expense_group_setting.save() + + assert len(expense_objects) == 3 + + ExpenseGroup.create_expense_groups_by_report_id_fund_source(expense_objects, 1) + + expense_group = ExpenseGroup.objects.filter(workspace=workspace).order_by('-created_at').first() + + assert expense_group.expenses.count() == 2 + + def test_create_expense_groups_by_report_id_fund_source(db): workspace_id = 4 payload = data['expenses'] diff --git a/tests/test_fyle/test_queue.py b/tests/test_fyle/test_queue.py new file mode 100644 index 00000000..e0e10f73 --- /dev/null +++ b/tests/test_fyle/test_queue.py @@ -0,0 +1,28 @@ +from apps.fyle.models import Expense +from apps.fyle.queue import async_post_accounting_export_summary +from apps.quickbooks_online.queue import __create_chain_and_run +from apps.workspaces.models import FyleCredential + + +# This test is just for cov :D +def test_async_post_accounting_export_summary(db): + async_post_accounting_export_summary(1, 1) + assert True + + +# This test is just for cov :D +def test_create_chain_and_run(db): + workspace_id = 3 + fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) + in_progress_expenses = Expense.objects.filter(org_id='or79Cob97KSh') + chain_tasks = [ + { + 'target': 'apps.quickbooks_online.tasks.create_cheque', + 'expense_group': 1, + 'task_log_id': 1, + 'last_export': True + } + ] + + __create_chain_and_run(fyle_credentials, in_progress_expenses, workspace_id, chain_tasks) + assert True diff --git a/tests/test_fyle/test_tasks.py b/tests/test_fyle/test_tasks.py index 5303efe0..54841ae3 100644 --- a/tests/test_fyle/test_tasks.py +++ b/tests/test_fyle/test_tasks.py @@ -1,13 +1,16 @@ import json from unittest import mock -import pytest +from django.db.models import Q from django.urls import reverse +import pytest + from apps.fyle.models import Expense, ExpenseGroup, ExpenseGroupSettings -from apps.fyle.tasks import create_expense_groups +from apps.fyle.tasks import create_expense_groups, post_accounting_export_summary +from apps.fyle.actions import mark_expenses_as_skipped from apps.tasks.models import TaskLog -from apps.workspaces.models import FyleCredential +from apps.workspaces.models import FyleCredential, Workspace from tests.helper import dict_compare_keys from tests.test_fyle.fixtures import data @@ -80,3 +83,22 @@ def test_create_expense_group_skipped_flow(mocker, api_client, test_connection): for expense in expenses: if expense.employee_email == 'jhonsnow@fyle.in': assert expense.is_skipped == True + + +def test_post_accounting_export_summary(db, mocker): + expense_group = ExpenseGroup.objects.filter(workspace_id=3).first() + expense_id = expense_group.expenses.first().id + expense_group.expenses.remove(expense_id) + + workspace = Workspace.objects.get(id=3) + mark_expenses_as_skipped(Q(), [expense_id], workspace) + + assert Expense.objects.filter(id=expense_id).first().accounting_export_summary['synced'] == False + + mocker.patch( + 'fyle_integrations_platform_connector.apis.Expenses.post_bulk_accounting_export_summary', + return_value=[] + ) + post_accounting_export_summary('or79Cob97KSh', 3) + + assert Expense.objects.filter(id=expense_id).first().accounting_export_summary['synced'] == True diff --git a/tests/test_mappings/test_tasks.py b/tests/test_mappings/test_tasks.py index 42c0c8dd..9759d6c9 100644 --- a/tests/test_mappings/test_tasks.py +++ b/tests/test_mappings/test_tasks.py @@ -45,6 +45,7 @@ from apps.workspaces.models import FyleCredential, QBOCredential, WorkspaceGeneralSettings from tests.helper import dict_compare_keys from tests.test_mappings.fixtures import data +from tests.test_fyle.fixtures import data as fyle_data def test_auto_create_tax_codes_mappings(db, mocker): @@ -86,13 +87,14 @@ def test_disable_category_for_items_mapping(db, mocker): workspace_general_setting.save() # mocking all the sdk calls - 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('fyle_integrations_platform_connector.apis.Categories.post_bulk', return_value=[]) mocker.patch('qbosdk.apis.Items.get', return_value=[]) # adding test data to the database destination_attribute = DestinationAttribute.objects.create(attribute_type='ACCOUNT', display_name='Item', value='Concrete', destination_id=3, workspace_id=workspace_id, active=False) expense_attribute = ExpenseAttribute.objects.create(attribute_type='CATEGORY', display_name='Category', value='Concrete', source_id='253737253737', 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) disable_category_for_items_mapping(workspace_id) @@ -228,14 +230,24 @@ def test_auto_create_category_mappings_with_items(db, mocker): assert mappings == 46 mocker.patch('qbosdk.apis.Accounts.get', 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('fyle_integrations_platform_connector.apis.Categories.post_bulk', return_value=[]) mocker.patch('qbosdk.apis.Items.get', return_value=[]) response = auto_create_category_mappings(workspace_id=workspace_id) assert response == [] + category = ExpenseAttribute.objects.filter(value='Patents & Licenses - Included', workspace_id=workspace_id).first() + + assert category != None + assert category.value == 'Patents & Licenses - Included' + + category = ExpenseAttribute.objects.filter(value='Patents & Licenses - Exempted', workspace_id=workspace_id).first() + + assert category == None + item_count = Mapping.objects.filter(destination_type='ACCOUNT', source_type='CATEGORY', destination__display_name='Item', workspace_id=workspace_id).count() + assert item_count == 0 @@ -452,7 +464,7 @@ def test_schedule_fyle_attributes_creation(db, mocker): def test_post_merchants(db, mocker): mocker.patch('fyle_integrations_platform_connector.apis.Merchants.get', return_value=data['get_merchants']) mocker.patch('fyle_integrations_platform_connector.apis.Merchants.post', return_value=[]) - mocker.patch('fyle_integrations_platform_connector.apis.Merchants.sync', return_value=[]) + mocker.patch('fyle.platform.apis.v1beta.admin.expense_fields', return_value=data['get_merchants']) workspace_id = 5 fyle_credentials = FyleCredential.objects.all() fyle_credentials = FyleCredential.objects.get(workspace_id=workspace_id) diff --git a/tests/test_quickbooks_online/fixtures.py b/tests/test_quickbooks_online/fixtures.py index d2e77bfe..42784ec2 100644 --- a/tests/test_quickbooks_online/fixtures.py +++ b/tests/test_quickbooks_online/fixtures.py @@ -146,18 +146,18 @@ 'PrivateNote': 'Credit card expense by sravan.kumar@fyle.in', 'Credit': None, 'Line': [ - { - 'Description': 'sravan.kumar@fyle.in - Concrete - 2023-07-06 - C/2023/04/R/6 - - None/app/main/#/enterprise/view_expense/txT4kpMbHdLg87L?org_id=or79Cob97KSh', - 'DetailType': 'ItemBasedExpenseLineDetail', - 'Amount': 1.0, - 'ItemBasedExpenseLineDetail': {'CustomerRef': {'value': None}, 'ClassRef': {'value': None}, 'TaxCodeRef': {'value': None}, 'BillableStatus': 'NotBillable', 'ItemRef': {'value': '3'}, 'Qty': 1}, - }, { 'Description': 'sravan.kumar@fyle.in - Food - 2023-07-06 - C/2023/04/R/6 - - None/app/main/#/enterprise/view_expense/txoF0nqv6cG78?org_id=or79Cob97KSh', 'DetailType': 'AccountBasedExpenseLineDetail', 'Amount': 10.0, 'AccountBasedExpenseLineDetail': {'CustomerRef': {'value': None}, 'ClassRef': {'value': None}, 'TaxCodeRef': {'value': None}, 'BillableStatus': 'NotBillable', 'AccountRef': {'value': '13'}, 'TaxAmount': 0.0}, }, + { + 'Description': 'sravan.kumar@fyle.in - Concrete - 2023-07-06 - C/2023/04/R/6 - - None/app/main/#/enterprise/view_expense/txT4kpMbHdLg87L?org_id=or79Cob97KSh', + 'DetailType': 'ItemBasedExpenseLineDetail', + 'Amount': 1.0, + 'ItemBasedExpenseLineDetail': {'CustomerRef': {'value': None}, 'ClassRef': {'value': None}, 'TaxCodeRef': {'value': None}, 'BillableStatus': 'NotBillable', 'ItemRef': {'value': '3'}, 'Qty': 1}, + }, ], }, "qbo_expense_payload": { @@ -269,18 +269,18 @@ "PrivateNote": "Reimbursable expense by user9@fyleforgotham.in", "Credit": "None", "Line": [ - { - "Description": "user9@fyleforgotham.in - Food - 2023-04-19 - C/2023/04/R/13 - - None/app/main/#/enterprise/view_expense/txT4kpMbiadw?org_id=or79Cob97KSh", - "DetailType": "AccountBasedExpenseLineDetail", - "Amount": 1.0, - "AccountBasedExpenseLineDetail": {"AccountRef": {"value": "13"}, "ClassRef": {"value": "None"}, "CustomerRef": {"value": "None"}, "TaxCodeRef": {"value": "None"}, "TaxAmount": 0.0, "BillableStatus": "NotBillable"}, - }, { "Description": "user9@fyleforgotham.in - Concrete - 2023-04-19 - C/2023/04/R/13 - - None/app/main/#/enterprise/view_expense/txT4kpKidaAdLm?org_id=or79Cob97KSh", "DetailType": "ItemBasedExpenseLineDetail", "Amount": 1.0, "ItemBasedExpenseLineDetail": {"ItemRef": {"value": "3"}, "Qty": 1, "CustomerRef": {"value": "None"}, "ClassRef": {"value": "None"}, "TaxCodeRef": {"value": "None"}, "BillableStatus": "NotBillable"}, }, + { + "Description": "user9@fyleforgotham.in - Food - 2023-04-19 - C/2023/04/R/13 - - None/app/main/#/enterprise/view_expense/txT4kpMbiadw?org_id=or79Cob97KSh", + "DetailType": "AccountBasedExpenseLineDetail", + "Amount": 1.0, + "AccountBasedExpenseLineDetail": {"AccountRef": {"value": "13"}, "ClassRef": {"value": "None"}, "CustomerRef": {"value": "None"}, "TaxCodeRef": {"value": "None"}, "TaxAmount": 0.0, "BillableStatus": "NotBillable"}, + }, ], }, "bill_response": { diff --git a/tests/test_quickbooks_online/test_actions.py b/tests/test_quickbooks_online/test_actions.py new file mode 100644 index 00000000..9b2be568 --- /dev/null +++ b/tests/test_quickbooks_online/test_actions.py @@ -0,0 +1,16 @@ +from apps.fyle.models import ExpenseGroup +from apps.quickbooks_online.actions import generate_export_url_and_update_expense + + +def test_generate_export_url_and_update_expense(db): + expense_group = ExpenseGroup.objects.filter(id=15).first() + generate_export_url_and_update_expense(expense_group) + + expense_group = ExpenseGroup.objects.filter(id=15).first() + + for expense in expense_group.expenses.all(): + assert expense.accounting_export_summary['url'] == 'https://lolo.qbo.com/app/expense?txnId=370' + assert expense.accounting_export_summary['synced'] == False + assert expense.accounting_export_summary['error_type'] == None + assert expense.accounting_export_summary['id'] == expense.expense_id + assert expense.accounting_export_summary['state'] == 'COMPLETE' diff --git a/tests/test_quickbooks_online/test_helpers.py b/tests/test_quickbooks_online/test_helpers.py new file mode 100644 index 00000000..e74ee041 --- /dev/null +++ b/tests/test_quickbooks_online/test_helpers.py @@ -0,0 +1,10 @@ +from apps.fyle.models import ExpenseGroup +from apps.quickbooks_online.helpers import generate_export_type_and_id + + +def test_generate_export_type_and_id(db): + expense_group = ExpenseGroup.objects.filter(id=15).first() + export_type, export_id = generate_export_type_and_id(expense_group) + + assert export_type == 'expense' + assert export_id == '370' diff --git a/tests/test_quickbooks_online/test_tasks.py b/tests/test_quickbooks_online/test_tasks.py index e8017bff..ed6014c7 100644 --- a/tests/test_quickbooks_online/test_tasks.py +++ b/tests/test_quickbooks_online/test_tasks.py @@ -985,7 +985,7 @@ def test_schedule_credit_card_purchase_creation(db): task_log.status = 'READY' task_log.save() - schedule_credit_card_purchase_creation(workspace_id, [17]) + schedule_credit_card_purchase_creation(workspace_id, [17], False) task_log = TaskLog.objects.filter(expense_group_id=expense_group.id).first() assert task_log.type == 'CREATING_CREDIT_CARD_PURCHASE' @@ -1002,7 +1002,7 @@ def test_schedule_bills_creation(db): task_log.status = 'READY' task_log.save() - schedule_bills_creation(workspace_id, [23]) + schedule_bills_creation(workspace_id, [23], False) task_log = TaskLog.objects.filter(expense_group_id=expense_group.id).first() assert task_log.type == 'CREATING_BILL' @@ -1019,7 +1019,7 @@ def test_schedule_cheques_creation(db): task_log.status = 'READY' task_log.save() - schedule_cheques_creation(workspace_id, [23]) + schedule_cheques_creation(workspace_id, [23], False) task_log = TaskLog.objects.filter(expense_group_id=expense_group.id).first() assert task_log.type == 'CREATING_CHECK' @@ -1036,7 +1036,7 @@ def test_schedule_qbo_expense_creation(db): task_log.status = 'READY' task_log.save() - schedule_qbo_expense_creation(workspace_id, [23]) + schedule_qbo_expense_creation(workspace_id, [23], False) task_log = TaskLog.objects.filter(expense_group_id=expense_group.id).first() assert task_log.type == 'CREATING_EXPENSE' @@ -1053,7 +1053,7 @@ def test_schedule_journal_entry_creation(db): task_log.status = 'READY' task_log.save() - schedule_journal_entry_creation(workspace_id, [23]) + schedule_journal_entry_creation(workspace_id, [23], False) task_log = TaskLog.objects.filter(expense_group_id=expense_group.id).first() assert task_log.type == 'CREATING_JOURNAL_ENTRY' diff --git a/tests/test_quickbooks_online/test_utils.py b/tests/test_quickbooks_online/test_utils.py index e07c837b..b26316cf 100644 --- a/tests/test_quickbooks_online/test_utils.py +++ b/tests/test_quickbooks_online/test_utils.py @@ -6,8 +6,8 @@ from fyle_accounting_mappings.models import DestinationAttribute, EmployeeMapping from qbosdk.exceptions import WrongParamsError -from apps.mappings.models import GeneralMapping from apps.fyle.models import ExpenseGroup +from apps.mappings.models import GeneralMapping from apps.quickbooks_online.utils import QBOConnector, QBOCredential, WorkspaceGeneralSettings from tests.helper import dict_compare_keys from tests.test_quickbooks_online.fixtures import data @@ -277,8 +277,9 @@ def test_construct_cheque_item_and_account_based(create_cheque_item_and_account_ cheque, cheque_lineitems = create_cheque_item_and_account_based cheque_object = qbo_connection._QBOConnector__construct_cheque(cheque=cheque, cheque_lineitems=cheque_lineitems) - assert cheque_object['Line'][1]['DetailType'] == 'ItemBasedExpenseLineDetail' - assert cheque_object['Line'][0]['DetailType'] == 'AccountBasedExpenseLineDetail' + assert cheque_object['Line'][0]['DetailType'] == 'ItemBasedExpenseLineDetail' + assert cheque_object['Line'][1]['DetailType'] == 'AccountBasedExpenseLineDetail' + assert dict_compare_keys(cheque_object, data['cheque_item_and_account_based_payload']) == [], 'construct cheque api return diffs in keys' diff --git a/tests/test_workspaces/test_actions.py b/tests/test_workspaces/test_actions.py new file mode 100644 index 00000000..922eb4aa --- /dev/null +++ b/tests/test_workspaces/test_actions.py @@ -0,0 +1,17 @@ +import pytest + +from apps.workspaces.actions import post_to_integration_settings + + +@pytest.mark.django_db(databases=['default']) +def test_post_to_integration_settings(mocker): + mocker.patch( + 'apps.fyle.helpers.post_request', + return_value='' + ) + + no_exception = True + post_to_integration_settings(1, True) + + # If exception is raised, this test will fail + assert no_exception diff --git a/tests/test_workspaces/test_apis/test_advanced_config/test_views.py b/tests/test_workspaces/test_apis/test_advanced_config/test_views.py index f03a938e..8db74b51 100644 --- a/tests/test_workspaces/test_apis/test_advanced_config/test_views.py +++ b/tests/test_workspaces/test_apis/test_advanced_config/test_views.py @@ -1,12 +1,21 @@ import json -from apps.workspaces.models import Workspace +import pytest + +from apps.workspaces.models import Workspace, FyleCredential from tests.helper import dict_compare_keys from tests.test_workspaces.test_apis.test_advanced_config.fixtures import data -def test_advanced_config(api_client, test_connection): - +@pytest.mark.django_db() +def test_advanced_config(api_client, test_connection, db): + FyleCredential.objects.update_or_create( + workspace_id=3, + defaults={ + 'refresh_token': 'ey.ey.ey', + 'cluster_domain': 'cluster_domain' + } + ) workspace = Workspace.objects.get(id=3) workspace.onboarding_state = 'ADVANCED_CONFIGURATION' workspace.save()