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

Setting Employee Mapping error, resolving and export exception decorator #445

Merged
merged 46 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
5c31b3f
onboarding state implementation
Oct 11, 2023
798e2bc
tests migrations
NileshPant1999 Oct 11, 2023
fdd7a9e
added onboarding state
Oct 11, 2023
609594d
changed comment
Oct 12, 2023
d15a8f5
added subsidiary state to onboarding state
Oct 12, 2023
ec4f4d1
changed script to add subsidiary state and fixed some bug
Oct 13, 2023
c64ff14
Merge branch 'master' into onboarding-state
Oct 13, 2023
67ddf59
bug fix
Oct 13, 2023
b9e5d3b
Merge branch 'master' into onboarding-state
Ashutosh619-sudo Oct 13, 2023
3bce538
state change on connection and subsidiary change
Oct 16, 2023
ac37706
Merge branch 'master' into onboarding-state-sub
Oct 16, 2023
4d291b3
map employees v2 api
Oct 19, 2023
173c5bb
map_employees typos
Oct 20, 2023
932cf3f
bug fix
Oct 20, 2023
8f6cbb7
Merge branch 'master' into v2-api
Oct 20, 2023
5972c5c
export setting changes
Oct 25, 2023
cfb791b
export settings V2 api
Oct 25, 2023
02095ab
Merge branch 'master' into v2-api
Ashutosh619-sudo Oct 25, 2023
e9a0b17
added test for export settings api
Oct 25, 2023
8016d3d
Merge branch 'v2-api' of https://github.com/fylein/fyle-netsuite-api …
Oct 25, 2023
aa56ca4
resolved comments
Oct 26, 2023
62697bb
import settings v2 api
Oct 26, 2023
35be2fd
Merge branch 'master' into v2-api
Oct 26, 2023
3357a2e
test added for import settings v2 api
Oct 26, 2023
3558f30
advanced settings v2 api
Oct 31, 2023
84ed5c9
advanced settings v2 api with test case
Oct 31, 2023
16225f1
Merge branch 'master' into v2-api
Oct 31, 2023
18ecafe
First schedule should be triggered after interval hours
Oct 31, 2023
adf33da
Handle Admin GET in a safer way
Oct 31, 2023
2faa99f
Making reimbursbale expense object nullable and checking edge cases f…
Nov 2, 2023
fe4f88b
comment resolved
Nov 3, 2023
e7b1d40
resolving comments
Nov 6, 2023
48a39fd
all comment resolved
Nov 6, 2023
2eb6d34
added code in test for the changes
Nov 6, 2023
a21e0c1
added test code for the changes
Nov 6, 2023
6c2c439
Error model, API and test
Nov 6, 2023
7c41782
Export error decorator, creating employee error and resolving.
Nov 7, 2023
a06936f
exception error removed
Nov 7, 2023
0a4aee3
import error resolved
Nov 7, 2023
469efda
employee mapping error test added
Nov 8, 2023
31696d2
Merge branch 'master' into employee_mapping_err
NileshPant1999 Nov 10, 2023
69a0ce7
Merge branch 'master' into employee_mapping_err
Ashutosh619-sudo Nov 21, 2023
dc81ada
made changes to the decorator for export functions
Ashutosh619-sudo Nov 21, 2023
24fabfa
Settings category mapping error and resolving (#446)
Ashutosh619-sudo Nov 27, 2023
cc17962
resolved comments
Nov 27, 2023
1c66ec1
resolved comments
Ashutosh619-sudo Nov 27, 2023
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
30 changes: 29 additions & 1 deletion apps/mappings/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,46 @@
from django.dispatch import receiver
from django_q.tasks import async_task

from fyle_accounting_mappings.models import MappingSetting
from fyle_accounting_mappings.models import MappingSetting, EmployeeMapping, Mapping, CategoryMapping

from apps.mappings.tasks import upload_attributes_to_fyle, schedule_cost_centers_creation,\
schedule_fyle_attributes_creation
from apps.mappings.helpers import schedule_or_delete_fyle_import_tasks
from apps.netsuite.helpers import schedule_payment_sync
from apps.workspaces.models import Configuration
from apps.workspaces.tasks import delete_cards_mapping_settings
from apps.tasks.models import Error

from .models import GeneralMapping, SubsidiaryMapping
from .tasks import schedule_auto_map_ccc_employees

@receiver(post_save, sender=Mapping)
def resolve_post_mapping_errors(sender, instance: Mapping, **kwargs):
"""
Resolve errors after mapping is created
"""
if instance.source_type == 'TAX_GROUP':
Error.objects.filter(expense_attribute_id=instance.source_id).update(
is_resolved=True
)

@receiver(post_save, sender=CategoryMapping)
def resolve_post_category_mapping_errors(sender, instance: Mapping, **kwargs):
"""
Resolve errors after mapping is created
"""
Error.objects.filter(expense_attribute_id=instance.source_category_id).update(
is_resolved=True
)

@receiver(post_save, sender=EmployeeMapping)
def resolve_post_employees_mapping_errors(sender, instance: Mapping, **kwargs):
"""
Resolve errors after mapping is created
"""
Error.objects.filter(expense_attribute_id=instance.source_employee_id).update(
is_resolved=True
)

@receiver(post_save, sender=SubsidiaryMapping)
def run_post_subsidiary_mappings(sender, instance: SubsidiaryMapping, **kwargs):
Expand Down
74 changes: 73 additions & 1 deletion apps/mappings/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,78 @@
from fyle.platform.exceptions import WrongParamsError, InvalidTokenError

from fyle_accounting_mappings.models import Mapping, MappingSetting, ExpenseAttribute, DestinationAttribute,\
CategoryMapping
CategoryMapping, EmployeeMapping
from fyle_accounting_mappings.helpers import EmployeesAutoMappingHelper

from fyle_integrations_platform_connector import PlatformConnector
from apps.mappings.models import GeneralMapping
from apps.netsuite.connector import NetSuiteConnector
from apps.workspaces.models import NetSuiteCredentials, FyleCredential, Configuration, Workspace
from apps.tasks.models import Error

from .exceptions import handle_exceptions
from .constants import FYLE_EXPENSE_SYSTEM_FIELDS, DEFAULT_NETSUITE_IMPORT_TYPES

logger = logging.getLogger(__name__)
logger.level = logging.INFO

def get_mapped_attributes_ids(source_attribute_type: str, destination_attribute_type: str, errored_attribute_ids: List[int]):

mapped_attribute_ids = []

if source_attribute_type == "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":
params = {
'source_employee_id__in': errored_attribute_ids,
}

if destination_attribute_type == "EMPLOYEE":
params['destination_employee_id__isnull'] = False
else:
params['destination_vendor_id__isnull'] = False
mapped_attribute_ids: List[int] = EmployeeMapping.objects.filter(
**params
).values_list('source_employee_id', flat=True)

elif source_attribute_type == "CATEGORY":
params = {
'source_category_id__in': errored_attribute_ids,
}

if destination_attribute_type == 'EXPENSE_TYPE':
params['destination_expense_head_id__isnull'] = False
else:
params['destination_account_id__isnull'] = False

mapped_attribute_ids: List[int] = CategoryMapping.objects.filter(
**params
).values_list('source_category_id', flat=True)

return mapped_attribute_ids


def resolve_expense_attribute_errors(
source_attribute_type: str, workspace_id: int, destination_attribute_type: str = None):
"""
Resolve Expense Attribute Errors
:return: None
"""
errored_attribute_ids: List[int] = Error.objects.filter(
is_resolved=False,
workspace_id=workspace_id,
type='{}_MAPPING'.format(source_attribute_type)
).values_list('expense_attribute_id', flat=True)

if errored_attribute_ids:
mapped_attribute_ids = get_mapped_attributes_ids(source_attribute_type, destination_attribute_type, errored_attribute_ids)

if mapped_attribute_ids:
Error.objects.filter(expense_attribute_id__in=mapped_attribute_ids).update(is_resolved=True)

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

Expand Down Expand Up @@ -467,6 +525,9 @@ def post_tax_groups(platform_connection: PlatformConnector, workspace_id: int):

platform_connection.tax_groups.sync()
Mapping.bulk_create_mappings(netsuite_attributes, 'TAX_GROUP', 'TAX_ITEM', workspace_id)
resolve_expense_attribute_errors(
source_attribute_type='TAX_GROUP', workspace_id=workspace_id
)

@handle_exceptions(task_name='Import Category to Fyle and Auto Create Mappings')
def auto_create_category_mappings(workspace_id):
Expand Down Expand Up @@ -505,6 +566,12 @@ def auto_create_category_mappings(workspace_id):
if reimbursable_expenses_object == 'EXPENSE REPORT' and \
corporate_credit_card_expenses_object in ('BILL', 'JOURNAL ENTRY', 'CREDIT CARD CHARGE'):
bulk_create_ccc_category_mappings(workspace_id)

resolve_expense_attribute_errors(
source_attribute_type="CATEGORY",
destination_attribute_type=reimbursable_destination_type,
workspace_id=workspace_id
)


def auto_import_and_map_fyle_fields(workspace_id):
Expand Down Expand Up @@ -657,6 +724,11 @@ def async_auto_map_employees(workspace_id: int):
netsuite_connection.sync_vendors()

EmployeesAutoMappingHelper(workspace_id, destination_type, employee_mapping_preference).reimburse_mapping()
resolve_expense_attribute_errors(
source_attribute_type="EMPLOYEE",
workspace_id=workspace_id,
destination_attribute_type=destination_type,
)


def schedule_auto_map_employees(employee_mapping_preference: str, workspace_id: int):
Expand Down
25 changes: 25 additions & 0 deletions apps/netsuite/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from apps.tasks.models import TaskLog
from apps.workspaces.models import LastExportDetail
from django.db.models import Q


def update_last_export_details(workspace_id):
last_export_detail = LastExportDetail.objects.get(workspace_id=workspace_id)

failed_exports = TaskLog.objects.filter(
~Q(type__in=['CREATING_VENDOR_PAYMENT','FETCHING_EXPENSES']), workspace_id=workspace_id, status__in=['FAILED', 'FATAL']
).count()

successful_exports = TaskLog.objects.filter(
~Q(type__in=['CREATING_VENDOR_PAYMENT', 'FETCHING_EXPENSES']),
workspace_id=workspace_id,
status='COMPLETE',
updated_at__gt=last_export_detail.last_exported_at
).count()

last_export_detail.failed_expense_groups_count = failed_exports
last_export_detail.successful_expense_groups_count = successful_exports
last_export_detail.total_expense_groups_count = failed_exports + successful_exports
last_export_detail.save()

return last_export_detail
160 changes: 160 additions & 0 deletions apps/netsuite/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import logging
import json
import traceback

from apps.fyle.models import ExpenseGroup
from apps.tasks.models import TaskLog, Error
from apps.workspaces.models import LastExportDetail, NetSuiteCredentials

from netsuitesdk.internal.exceptions import NetSuiteRequestError
from netsuitesdk import NetSuiteRateLimitError, NetSuiteLoginError
from fyle_netsuite_api.exceptions import BulkError

from .actions import update_last_export_details

from django.db.models import Q

logger = logging.getLogger(__name__)
logger.level = logging.INFO

netsuite_error_message = 'NetSuite System Error'

def __handle_netsuite_connection_error(expense_group: ExpenseGroup, task_log: TaskLog) -> None:
logger.info(
'NetSuite Credentials not found for workspace_id %s / expense group %s',
expense_group.id,
expense_group.workspace_id
)
detail = {
'message': 'NetSuite Account not connected'
}

Error.objects.update_or_create(
workspace_id=expense_group.workspace_id,
expense_group=expense_group,
defaults={
'type': 'NETSUITE_ERROR',
'error_title': netsuite_error_message,
'error_detail': detail['message'],
'is_resolved': False
})

task_log.status = 'FAILED'
task_log.detail = detail

task_log.save()


def __log_error(task_log: TaskLog) -> None:
logger.exception('Something unexpected happened workspace_id: %s %s', task_log.workspace_id, task_log.detail)


def handle_netsuite_exceptions(payment=False):
def decorator(func):
def wrapper(*args):
if payment:
entity_object = args[0]
workspace_id = args[1]
object_type = args[2]
task_log, _ = TaskLog.objects.update_or_create(
workspace_id=workspace_id,
task_id='PAYMENT_{}'.format(entity_object['unique_id']),
defaults={
'status': 'IN_PROGRESS',
'type': 'CREATING_VENDOR_PAYMENT'
}
)
else:
expense_group = args[0]
task_log_id = args[1]
task_log = TaskLog.objects.get(id=task_log_id)
last_export = args[2]

try:
func(*args)

except NetSuiteCredentials.DoesNotExist:
if payment:
logger.info(
'NetSuite Credentials not found for workspace_id %s',
workspace_id
)
detail = {
'message': 'NetSuite Account not connected'
}
task_log.status = 'FAILED'
task_log.detail = detail

task_log.save()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why special treatment for payment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the payment function there is no expense_group

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also the Error Model needs expense_group

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can assign expense_group = None inside 55 line block and then in __handle_netsuite_connection_error(), check for expense_group existence and update or create things

else:
__handle_netsuite_connection_error(expense_group, task_log)

except (NetSuiteRequestError, NetSuiteLoginError) as exception:
all_details = []
logger.info({'error': exception})
detail = json.dumps(exception.__dict__)
detail = json.loads(detail)
task_log.status = 'FAILED'

all_details.append({
'value': netsuite_error_message,
'type': detail['code'],
'message': detail['message']
})
if not payment:
Error.objects.update_or_create(
workspace_id=expense_group.workspace_id,
expense_group=expense_group,
defaults={
'type': 'NETSUITE_ERROR',
'error_title': netsuite_error_message,
'error_detail': detail['message'],
'is_resolved': False
}
)
task_log.detail = all_details

task_log.save()

except BulkError as exception:
logger.info(exception.response)
detail = exception.response
task_log.status = 'FAILED'
task_log.detail = detail

task_log.save()

except NetSuiteRateLimitError:
if not payment:
Error.objects.update_or_create(
workspace_id=expense_group.workspace_id,
expense_group=expense_group,
defaults={
'type': 'NETSUITE_ERROR',
'error_title': netsuite_error_message,
'error_detail': f'Rate limit error, workspace_id - {expense_group.workspace_id}',
'is_resolved': False
}
)
logger.info('Rate limit error, workspace_id - %s', workspace_id if payment else expense_group.workspace_id)
task_log.status = 'FAILED'
task_log.detail = {
'error': 'Rate limit error'
}

task_log.save()

except Exception:
error = traceback.format_exc()
task_log.detail = {
'error': error
}
task_log.status = 'FATAL'
task_log.save()
__log_error(task_log)

if not payment and last_export is True:
update_last_export_details(expense_group.workspace_id)

return wrapper
return decorator
Loading
Loading