Skip to content

Commit

Permalink
feature: support of error parser for expense report export (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
NileshPant1999 authored May 30, 2023
1 parent 8a14ba5 commit bafb51a
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 5 deletions.
4 changes: 3 additions & 1 deletion netsuitesdk/api/expense_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from netsuitesdk.internal.utils import PaginatedSearch


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -97,5 +98,6 @@ def post(self, data) -> OrderedDict:
er['entity'] = self.ns_client.RecordRef(**(data['entity']))

logger.debug('able to create er = %s', er)
res = self.ns_client.upsert(er)
res = self.ns_client.upsert(er, 'expense_report')

return self._serialize(res)
Empty file added netsuitesdk/errors/__init__.py
Empty file.
18 changes: 18 additions & 0 deletions netsuitesdk/errors/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

error_reference = {
"expense_report":
{
'category_reference_error': {'regex': r"An error occured in a upsert request: Invalid category reference key \d+ for entity \d+", 'keys': ['expense_category', 'employee']},
'account_reference_error': {'regex': r"An error occured in a upsert request: Invalid account reference key \d+ for subsidiary \d+", 'keys': ['account', 'subsidiary']},
'project_reference_error': {'regex': r"An error occured in a upsert request: Invalid customer reference key \d+ for entity \d+", 'keys': ['customer', 'employee']},
'location_reference_error': {'regex': r"An error occured in a upsert request: Invalid location reference key \d+ for subsidiary \d+", 'keys': ['location', 'subsidiary']},
'department_reference_error': {'regex':r"An error occured in a upsert request: Invalid department reference key \d+ for subsidiary \d+" , 'keys': ['department', 'subsidiary']},
'currency_reference_error': {'regex': r"An error occured in a upsert request: Invalid currency reference key \d+ for subsidiary \d+", 'keys': ['currency', 'subsidiary']}
}
}

list_of_dicts = [
['expense_category', 'employee'], ['account', 'subsidiary'],
['customer', 'employee'], ['location', 'subsidiary'],
['department', 'subsidiary'], ['currency', 'subsidiary']
]
20 changes: 20 additions & 0 deletions netsuitesdk/errors/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import re
from .errors import error_reference


def replace_numbers(string , replacement1, replacement2, number1, number2):
replaced_string = re.sub(r'\b({}|{})\b'.format(number1, number2), lambda m: replacement1 if m.group() == number1 else replacement2, string)
return replaced_string


def convert_to_camelcase(word):
return ''.join(word.title().split('_'))


def export_error_matcher(string, export_type):
for _, error_data in error_reference[export_type].items():
if re.match(error_data['regex'], string):
numbers = re.findall(r'\d+', string)
return {key: int(number) for key, number in zip(error_data['keys'], numbers)}

return {}
35 changes: 35 additions & 0 deletions netsuitesdk/errors/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from .helpers import replace_numbers, convert_to_camelcase
from .errors import list_of_dicts

class ErrorParser():

def __init__(self, get_instance):
self.get_instance = get_instance

def get_entity_values(self, error_dict):

entity_keys = list(error_dict)
object_1 = self.get_instance(convert_to_camelcase(entity_keys[0]), error_dict[entity_keys[0]])
object_2 = self.get_instance(convert_to_camelcase(entity_keys[1]), error_dict[entity_keys[1]])

if object_1 and object_2:
if entity_keys[1] == 'employee':
object_2 = object_2['email'] if object_2['email'] else object_2['firstName'] + " " + object_2['lastName']
return object_1['name'], object_2

if entity_keys[0] == 'account':
object_1 = object_1['acctName']
return object_1, object_2['name']

return object_1['name'], object_2['name']


def export_error_parser(self, error_dict, message):

parsed_message = message
if list(error_dict) in list_of_dicts:
object_1, object_2 = self.get_entity_values(error_dict)
entity_keys = list(error_dict)
parsed_message = replace_numbers(message, object_1, object_2, error_dict[entity_keys[0]], error_dict[entity_keys[1]])

return parsed_message
16 changes: 13 additions & 3 deletions netsuitesdk/internal/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
from .netsuite_types import *


from netsuitesdk.errors.parser import ErrorParser
from netsuitesdk.errors.helpers import export_error_matcher


class NetSuiteClient:
"""The Netsuite client class providing access to the Netsuite
SOAP/WSDL web service"""
Expand Down Expand Up @@ -289,6 +293,7 @@ def logout(self):
def _request_error(self, service_name, detail, error_cls=None):
if error_cls is None:
error_cls = NetSuiteRequestError

exc = error_cls(
"An error occured in a {service_name} request: {msg}".format(
service_name=service_name,
Expand Down Expand Up @@ -366,7 +371,6 @@ def get(self, recordType, internalId=None, externalId=None):
:rtype: Record
:raises ValueError: if neither internalId nor externalId was passed
"""

recordType = recordType[0].lower() + recordType[1:]
if internalId is not None:
record_ref = self.RecordRef(type=recordType, internalId=internalId)
Expand All @@ -377,7 +381,6 @@ def get(self, recordType, internalId=None, externalId=None):

response = self.request('get', baseRef=record_ref)
response = response.body.readResponse

status = response.status
if status.isSuccess:
record = response['record']
Expand Down Expand Up @@ -482,7 +485,7 @@ def searchMoreWithId(self, searchId, pageIndex):
exc = self._request_error('searchMoreWithId', detail=status['statusDetail'][0])
raise exc

def upsert(self, record):
def upsert(self, record, record_type=None):
"""
Add an object of type recordType with given externalId..
If a record of specified type with matching externalId already
Expand Down Expand Up @@ -512,6 +515,13 @@ def upsert(self, record):
return record_ref
else:
exc = self._request_error('upsert', detail=status['statusDetail'][0])

if record_type:
error_parser = ErrorParser(self.get)
error_dict = export_error_matcher(exc.message, 'expense_report')
message = error_parser.export_error_parser(error_dict, exc.message)
exc.message = message

raise exc

def basic_stringfield_search(self, type_name, attribute, value, operator=None):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name='netsuitesdk',
version='2.17.1',
version='2.18.0',
author='Siva Narayanan',
author_email='[email protected]',
description='Python SDK for accessing the NetSuite SOAP webservice',
Expand Down
33 changes: 33 additions & 0 deletions test/internal/test_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest
from netsuitesdk.errors.helpers import replace_numbers, convert_to_camelcase, export_error_matcher

errors = [
('An error occured in a upsert request: Invalid category reference key 1 for entity 12', {'expense_category': 1, 'employee': 12}),
('An error occured in a upsert request: Invalid account reference key 1 for subsidiary 12', {'account': 1, 'subsidiary': 12}),
('An error occured in a upsert request: Invalid customer reference key 1 for entity 12', {'customer': 1, 'employee': 12}),
('An error occured in a upsert request: Invalid location reference key 1 for subsidiary 12', {'location': 1, 'subsidiary': 12}),
('An error occured in a upsert request: Invalid department reference key 1 for subsidiary 12', {'department': 1, 'subsidiary': 12}),
('An error occured in a upsert request: Invalid currency reference key 1 for subsidiary 12', {'currency': 1, 'subsidiary': 12})
]

def test_replace_number():
final_string = "An error occured in a upsert request: Invalid category reference key Travel for entity John Doe"
replaced_string = replace_numbers('An error occured in a upsert request: Invalid category reference key 1 for entity 2', 'Travel', 'John Doe', '1', '2')
assert final_string == replaced_string


def test_convert_to_camelcase():
entity_type = 'ExpenseCategory'
result = convert_to_camelcase('expense_category')
assert entity_type == result

entity_type = 'Currency'
result = convert_to_camelcase('currency')
assert entity_type == result


@pytest.mark.parametrize("input, output", errors)
def test_export_error_matcher(input, output):

result = export_error_matcher(input, 'expense_report')
assert result == output
14 changes: 14 additions & 0 deletions test/internal/test_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import pytest
from netsuitesdk.errors.parser import ErrorParser


def test_export_error_parser(mocker, ns):

mocker.patch(
'netsuitesdk.errors.parser.ErrorParser.get_entity_values',
return_value={'Travel', 'John Doe'}
)

parser = ErrorParser(ns)
result = parser.export_error_parser({'expense_category': '1', 'employee': '12'}, 'An error occured in a upsert request: Invalid category reference key 1 for entity 12')
assert result == "An error occured in a upsert request: Invalid category reference key Travel for entity John Doe"
37 changes: 37 additions & 0 deletions test/internal/test_upsert.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from netsuitesdk.internal.utils import PaginatedSearch
from netsuitesdk.internal.exceptions import NetSuiteRequestError
import logging
import pytest
import zeep
Expand Down Expand Up @@ -118,6 +119,42 @@ def test_upsert_journal_entry(ns):
assert (je['externalId'] == 'JE_1234'), 'Journal Entry External ID does not match'


def test_failed_expense_report(ns):
employee_ref = ns.RecordRef(type='employee', internalId=get_employee(ns).internalId)
cat_account_ref = ns.RecordRef(type='account', internalId='3')
loc_ref = ns.RecordRef(type='location', internalId=12)
subs_ref = ns.RecordRef(type='subsdiary', internalId=5)
dep_ref = ns.RecordRef(type='department', internalId=get_department(ns).internalId)
class_ref = ns.RecordRef(type='classification', internalId=get_department(ns).internalId)
currency_ref = ns.RecordRef(type='currency', internalId=get_currency(ns).internalId)
expenses = []

er = ns.ExpenseReportExpense()
er['category'] = cat_account_ref
er['amount'] = 10.0
er['department'] = dep_ref
er['class'] = class_ref
er['location'] = loc_ref
er['currency'] = currency_ref
er['subsdiary'] = subs_ref

expenses.append(er)

expense_report = ns.ExpenseReport(externalId='EXPR_1')
expense_report['expenseReportCurrency'] = currency_ref # US dollar
expense_report['exchangerate'] = 1.0
expense_report['expenseList'] = ns.ExpenseReportExpenseList(expense=expenses)
expense_report['memo'] = 'test memo'
expense_report['entity'] = employee_ref
logger.debug('upserting expense report %s', expense_report)

try:
record_ref = ns.upsert(expense_report, record_type='expense_report')
logger.debug('record_ref = %s', record_ref)
except NetSuiteRequestError as e:
assert e.message == 'An error occured in a upsert request: Invalid location reference key Honeycomb Mfg. for subsidiary Honeycomb Mfg..'
assert e.code == 'INVALID_KEY_OR_REF'

def test_upsert_expense_report(ns):
employee_ref = ns.RecordRef(type='employee', internalId=get_employee(ns).internalId)
bill_account_ref = ns.RecordRef(type='account', internalId=25)
Expand Down

0 comments on commit bafb51a

Please sign in to comment.