Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImportTax + Vendors : Scheduling #543

Merged
merged 4 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 0 additions & 15 deletions apps/mappings/helpers.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
from datetime import datetime

from django_q.models import Schedule
from fyle_accounting_mappings.models import MappingSetting

from apps.workspaces.models import WorkspaceGeneralSettings


def schedule_or_delete_fyle_import_tasks(configuration: WorkspaceGeneralSettings):
"""
:param configuration: WorkspaceGeneralSettings Instance
:return: None
"""
if configuration.import_vendors_as_merchants:
start_datetime = datetime.now()
Schedule.objects.update_or_create(func='apps.mappings.tasks.auto_import_and_map_fyle_fields', args='{}'.format(configuration.workspace_id), defaults={'schedule_type': Schedule.MINUTES, 'minutes': 24 * 60, 'next_run': start_datetime})
else:
Schedule.objects.filter(func='apps.mappings.tasks.auto_import_and_map_fyle_fields', args='{}'.format(configuration.workspace_id)).delete()


def get_auto_sync_permission(workspace_general_settings: WorkspaceGeneralSettings, mapping_setting: MappingSetting = None):
"""
Get the auto sync permission
Expand Down
26 changes: 16 additions & 10 deletions apps/mappings/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,6 @@ def schedule_auto_map_ccc_employees(workspace_id: int):
schedule.delete()


def schedule_tax_groups_creation(import_tax_codes, workspace_id):
if import_tax_codes:
schedule, _ = Schedule.objects.update_or_create(func='apps.mappings.tasks.auto_create_tax_codes_mappings', args='{}'.format(workspace_id), defaults={'schedule_type': Schedule.MINUTES, 'minutes': 24 * 60, 'next_run': datetime.now()})
else:
schedule: Schedule = Schedule.objects.filter(func='apps.mappings.tasks.auto_create_tax_codes_mappings', args='{}'.format(workspace_id)).first()

if schedule:
schedule.delete()


def schedule_auto_map_employees(employee_mapping_preference: str, workspace_id: int):
if employee_mapping_preference:
start_datetime = datetime.now()
Expand Down Expand Up @@ -94,6 +84,22 @@ def construct_tasks_and_chain_import_fields_to_fyle(workspace_id):
'charts_of_accounts': workspace_general_settings.charts_of_accounts if 'accounts' in destination_sync_methods else None,
}

if workspace_general_settings.import_tax_codes:
task_settings['import_tax'] = {
'destination_field': 'TAX_CODE',
'destination_sync_methods': [SYNC_METHODS['TAX_CODE']],
'is_auto_sync_enabled': get_auto_sync_permission(workspace_general_settings),
'is_3d_mapping': False,
}

if workspace_general_settings.import_vendors_as_merchants:
task_settings['import_vendors_as_merchants'] = {
'destination_field': 'VENDOR',
'destination_sync_methods': [SYNC_METHODS['VENDOR']],
'is_auto_sync_enabled': get_auto_sync_permission(workspace_general_settings),
'is_3d_mapping': False,
}

if not workspace_general_settings.import_items:
task_settings['import_items'] = False

Expand Down
17 changes: 5 additions & 12 deletions apps/mappings/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe
"""
task_to_be_scheduled = None
for mapping_setting in mapping_settings:
if mapping_setting['import_to_fyle'] and mapping_setting['source_field'] in ['PROJECT', 'COST_CENTER'] or mapping_setting['is_custom']:
if mapping_setting['import_to_fyle']:
task_to_be_scheduled = True
break

if task_to_be_scheduled or workspace_general_settings.import_categories or workspace_general_settings.import_items:
if task_to_be_scheduled or workspace_general_settings.import_categories or workspace_general_settings.import_items\
or workspace_general_settings.import_tax_codes or workspace_general_settings.import_vendors_as_merchants:
Schedule.objects.update_or_create(
func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle',
args='{}'.format(workspace_general_settings.workspace_id),
Expand All @@ -32,19 +33,11 @@ def schedule_or_delete_fyle_import_tasks(workspace_general_settings: WorkspaceGe
else:
import_fields_count = MappingSetting.objects.filter(
import_to_fyle=True,
workspace_id=workspace_general_settings.workspace_id,
source_field__in=['PROJECT', 'COST_CENTER']
).count()

custom_field_import_fields_count = MappingSetting.objects.filter(
import_to_fyle=True,
workspace_id=workspace_general_settings.workspace_id,
is_custom=True
workspace_id=workspace_general_settings.workspace_id
).count()

# If the import fields count is 0, delete the schedule
if import_fields_count == 0 and custom_field_import_fields_count == 0\
and not workspace_general_settings.import_categories and not workspace_general_settings.import_items:
if import_fields_count == 0:
Schedule.objects.filter(
func='apps.mappings.queues.construct_tasks_and_chain_import_fields_to_fyle',
args='{}'.format(workspace_general_settings.workspace_id)
Expand Down
2 changes: 1 addition & 1 deletion apps/mappings/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def resolve_post_mapping_errors(sender, instance: Mapping, **kwargs):
"""
Resolve errors after mapping is created
"""
if instance.source_type in ('CATEGORY', 'TAX_GROUP'):
if instance.source_type in ('CATEGORY'):
error = Error.objects.filter(expense_attribute_id=instance.source_id).first()
if error:
error.is_resolved = True
Expand Down
168 changes: 3 additions & 165 deletions apps/mappings/tasks.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import logging
from typing import Dict, List
from typing import List

from django_q.tasks import Chain
from fyle_accounting_mappings.models import DestinationAttribute, EmployeeMapping, ExpenseAttribute, Mapping, MappingSetting
from fyle_accounting_mappings.models import DestinationAttribute, EmployeeMapping, ExpenseAttribute
from fyle_integrations_platform_connector import PlatformConnector

from apps.mappings.exceptions import handle_import_exceptions
Expand All @@ -14,29 +13,6 @@
logger = logging.getLogger(__name__)
logger.level = logging.INFO

DEFAULT_FYLE_CATEGORIES = [
'train',
'fuel',
'office supplies',
'utility',
'entertainment',
'others',
'mileage',
'food',
'per diem',
'bus',
'taxi',
'mail',
'lodging',
'professional services',
'airlines',
'software',
'parking',
'unspecified',
'rental',
'groceries',
]


def resolve_expense_attribute_errors(source_attribute_type: str, workspace_id: int, destination_attribute_type: str = None):
"""
Expand All @@ -48,10 +24,7 @@ def resolve_expense_attribute_errors(source_attribute_type: str, workspace_id: i
if errored_attribute_ids:
mapped_attribute_ids = []

if source_attribute_type in ('TAX_GROUP'):
mapped_attribute_ids: List[int] = Mapping.objects.filter(source_id__in=errored_attribute_ids).values_list('source_id', flat=True)

elif source_attribute_type == 'EMPLOYEE':
if source_attribute_type == 'EMPLOYEE':
if destination_attribute_type == 'EMPLOYEE':
params = {'source_employee_id__in': errored_attribute_ids, 'destination_employee_id__isnull': False}
else:
Expand All @@ -62,38 +35,6 @@ def resolve_expense_attribute_errors(source_attribute_type: str, workspace_id: i
Error.objects.filter(expense_attribute_id__in=mapped_attribute_ids).update(is_resolved=True)


def remove_duplicates(qbo_attributes: List[DestinationAttribute]):
unique_attributes = []

attribute_values = []

for attribute in qbo_attributes:
if attribute.value.lower() not in attribute_values:
unique_attributes.append(attribute)
attribute_values.append(attribute.value.lower())

return unique_attributes


@handle_import_exceptions(task_name='Auto Create Tax Code Mappings')
def auto_create_tax_codes_mappings(workspace_id: int):
"""
Create Tax Codes Mappings
:return: None
"""

fyle_credentials: FyleCredential = FyleCredential.objects.get(workspace_id=workspace_id)

platform = PlatformConnector(fyle_credentials)

platform.tax_groups.sync()

mapping_setting = MappingSetting.objects.get(source_field='TAX_GROUP', workspace_id=workspace_id)

sync_qbo_attribute(mapping_setting.destination_field, workspace_id)
upload_tax_groups_to_fyle(platform, workspace_id)


def get_existing_source_and_mappings(destination_type: str, workspace_id: int):
existing_mappings = EmployeeMapping.objects.filter(workspace_id=workspace_id).all()

Expand Down Expand Up @@ -318,106 +259,3 @@ def async_auto_map_ccc_account(workspace_id: int):
platform.employees.sync()

auto_map_ccc_employees(default_ccc_account_id, workspace_id)


def upload_tax_groups_to_fyle(platform_connection: PlatformConnector, workspace_id: int):
existing_tax_codes_name = ExpenseAttribute.objects.filter(attribute_type='TAX_GROUP', workspace_id=workspace_id).values_list('value', flat=True)

qbo_attributes = DestinationAttribute.objects.filter(attribute_type='TAX_CODE', workspace_id=workspace_id).order_by('value', 'id')

qbo_attributes = remove_duplicates(qbo_attributes)

fyle_payload: List[Dict] = create_fyle_tax_group_payload(qbo_attributes, existing_tax_codes_name)

if fyle_payload:
platform_connection.tax_groups.post_bulk(fyle_payload)

platform_connection.tax_groups.sync()
Mapping.bulk_create_mappings(qbo_attributes, 'TAX_GROUP', 'TAX_CODE', workspace_id)
resolve_expense_attribute_errors(source_attribute_type='TAX_GROUP', workspace_id=workspace_id)


def sync_qbo_attribute(qbo_attribute_type: str, workspace_id: int):
qbo_credentials = QBOCredential.get_active_qbo_credentials(workspace_id)
qbo_connection = QBOConnector(credentials_object=qbo_credentials, workspace_id=workspace_id)

if qbo_attribute_type == 'CUSTOMER':
qbo_connection.sync_customers()

elif qbo_attribute_type == 'DEPARTMENT':
qbo_connection.sync_departments()

elif qbo_attribute_type == 'CLASS':
qbo_connection.sync_classes()

elif qbo_attribute_type == 'TAX_CODE':
qbo_connection.sync_tax_codes()

elif qbo_attribute_type == 'VENDOR':
qbo_connection.sync_vendors()


def create_fyle_tax_group_payload(qbo_attributes: List[DestinationAttribute], existing_fyle_tax_groups: list):
"""
Create Fyle Cost Centers Payload from QBO Objects
:param existing_fyle_tax_groups: Existing cost center names
:param qbo_attributes: QBO Objects
:return: Fyle Cost Centers Payload
"""

fyle_tax_group_payload = []
for qbo_attribute in qbo_attributes:
if qbo_attribute.value not in existing_fyle_tax_groups:
fyle_tax_group_payload.append({'name': qbo_attribute.value, 'is_enabled': True, 'percentage': round((qbo_attribute.detail['tax_rate'] / 100), 2)})

return fyle_tax_group_payload


def create_fyle_merchants_payload(vendors, existing_merchants_name):
payload: List[str] = []
for vendor in vendors:
if vendor.value not in existing_merchants_name:
payload.append(vendor.value)

return payload


def post_merchants(platform_connection: PlatformConnector, workspace_id: int):
existing_merchants_name = ExpenseAttribute.objects.filter(attribute_type='MERCHANT', workspace_id=workspace_id).values_list('value', flat=True)

qbo_attributes = DestinationAttribute.objects.filter(attribute_type='VENDOR', active=True, workspace_id=workspace_id).order_by('value', 'id')

qbo_attributes = remove_duplicates(qbo_attributes)

fyle_payload: List[str] = create_fyle_merchants_payload(qbo_attributes, existing_merchants_name)

if fyle_payload:
platform_connection.merchants.post(fyle_payload)
platform_connection.merchants.sync()


@handle_import_exceptions(task_name='Auto Create Vendors as Merchants')
def auto_create_vendors_as_merchants(workspace_id):
fyle_credentials: FyleCredential = FyleCredential.objects.get(workspace_id=workspace_id)

fyle_connection = PlatformConnector(fyle_credentials)

fyle_connection.merchants.sync()

sync_qbo_attribute('VENDOR', workspace_id)
post_merchants(fyle_connection, workspace_id)


def auto_import_and_map_fyle_fields(workspace_id):
"""
Auto import and map fyle fields
"""
workspace_general_settings: WorkspaceGeneralSettings = WorkspaceGeneralSettings.objects.get(workspace_id=workspace_id)

chain = Chain()

if workspace_general_settings.import_vendors_as_merchants:
chain.append('apps.mappings.tasks.auto_create_vendors_as_merchants', workspace_id)

if chain.length() > 0:
chain.run()
2 changes: 1 addition & 1 deletion apps/workspaces/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from apps.workspaces.models import FyleCredential, LastExportDetail, QBOCredential, Workspace, WorkspaceGeneralSettings
from apps.workspaces.serializers import QBOCredentialSerializer
from apps.workspaces.signals import post_delete_qbo_connection
from apps.workspaces.utils import assert_valid
from fyle_qbo_api.utils import assert_valid

User = get_user_model()
logger = logging.getLogger(__name__)
Expand Down
10 changes: 1 addition & 9 deletions apps/workspaces/apis/import_settings/triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
from fyle_accounting_mappings.models import MappingSetting

from apps.fyle.models import ExpenseGroupSettings
from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks
from apps.mappings.queues import (
schedule_tax_groups_creation
)
from apps.workspaces.models import WorkspaceGeneralSettings
from apps.mappings.schedules import schedule_or_delete_fyle_import_tasks as new_schedule_or_delete_fyle_import_tasks

Expand Down Expand Up @@ -76,9 +72,6 @@ def post_save_workspace_general_settings(self, workspace_general_settings_instan
"""
Post save action for workspace general settings
"""
schedule_tax_groups_creation(import_tax_codes=self.__workspace_general_settings.get('import_tax_codes'), workspace_id=self.__workspace_id)

schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance)
new_schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance)

def __remove_old_department_source_field(self, current_mappings_settings: List[MappingSetting], new_mappings_settings: List[Dict]):
Expand Down Expand Up @@ -111,9 +104,8 @@ def post_save_mapping_settings(self, workspace_general_settings_instance: Worksp
for setting in self.__mapping_settings:
destination_fields.append(setting['destination_field'])

MappingSetting.objects.filter(~Q(destination_field__in=destination_fields), destination_field__in=['CLASS', 'CUSTOMER', 'DEPARTMENT'], workspace_id=self.__workspace_id).delete()
MappingSetting.objects.filter(~Q(destination_field__in=destination_fields), destination_field__in=['CLASS', 'CUSTOMER', 'DEPARTMENT', 'TAX_CODE'], workspace_id=self.__workspace_id).delete()

self.__update_expense_group_settings_for_departments()

new_schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance, self.__mapping_settings)
schedule_or_delete_fyle_import_tasks(workspace_general_settings_instance)
Loading
Loading