Skip to content

Commit

Permalink
Netsuite SDK support for Employees and Expense Reports
Browse files Browse the repository at this point in the history
  • Loading branch information
Sravanksk authored Jul 13, 2020
1 parent 874b31e commit 2057f94
Show file tree
Hide file tree
Showing 10 changed files with 281 additions and 2 deletions.
9 changes: 9 additions & 0 deletions netsuitesdk/api/employees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .base import ApiBase
import logging

logger = logging.getLogger(__name__)


class Employees(ApiBase):
def __init__(self, ns_client):
ApiBase.__init__(self, ns_client=ns_client, type_name='Employee')
63 changes: 63 additions & 0 deletions netsuitesdk/api/expense_reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from collections import OrderedDict

from .base import ApiBase
import logging

from netsuitesdk.internal.utils import PaginatedSearch

logger = logging.getLogger(__name__)


class ExpenseReports(ApiBase):
"""
ExpenseReports are not directly searchable - only via as employees
"""

def __init__(self, ns_client):
ApiBase.__init__(self, ns_client=ns_client, type_name='ExpenseReport')

def get_all_generator(self):
record_type_search_field = self.ns_client.SearchStringField(searchValue='ExpenseReport', operator='contains')
basic_search = self.ns_client.basic_search_factory('Employee', recordType=record_type_search_field)
paginated_search = PaginatedSearch(client=self.ns_client,
type_name='Employee',
basic_search=basic_search,
pageSize=20)
return self._paginated_search_to_generator(paginated_search=paginated_search)

def post(self, data) -> OrderedDict:
assert data['externalId'], 'missing external id'
er = self.ns_client.ExpenseReport()
expense_list = []
for eod in data['expenseList']:
ere = self.ns_client.ExpenseReportExpense(**eod)
expense_list.append(ere)

er['expenseList'] = self.ns_client.ExpenseReportExpenseList(expense=expense_list)
er['expenseReportCurrency'] = self.ns_client.RecordRef(**(data['expenseReportCurrency']))

if 'memo' in data:
er['memo'] = data['memo']

if 'tranId' in data:
er['tranId'] = data['tranId']

if 'class' in data:
er['class'] = data['class']

if 'location' in data:
er['location'] = data['location']

if 'department' in data:
er['department'] = data['department']

if 'account' in data:
er['account'] = self.ns_client.RecordRef(**(data['account']))

if 'externalId' in data:
er['externalId'] = data['externalId']

er['entity'] = self.ns_client.RecordRef(**(data['entity']))
logger.debug('able to create er = %s', er)
res = self.ns_client.upsert(er)
return self._serialize(res)
4 changes: 4 additions & 0 deletions netsuitesdk/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from .api.vendors import Vendors
from .api.subsidiaries import Subsidiaries
from .api.journal_entries import JournalEntries
from .api.employees import Employees
from .api.expense_reports import ExpenseReports
from .internal.client import NetSuiteClient


Expand All @@ -28,3 +30,5 @@ def __init__(self, account, consumer_key, consumer_secret, token_key, token_secr
self.vendors = Vendors(ns_client)
self.subsidiaries = Subsidiaries(ns_client)
self.journal_entries = JournalEntries(ns_client)
self.employees = Employees(ns_client)
self.expense_reports = ExpenseReports(ns_client)
14 changes: 14 additions & 0 deletions netsuitesdk/internal/netsuite_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'TransactionSearchBasic',
'VendorSearchBasic',
'SubsidiarySearchBasic',
'EmployeeSearchBasic',
],

# urn:relationships.lists.webservices.netsuite.com
Expand Down Expand Up @@ -84,6 +85,19 @@
'JournalEntryLine',
'JournalEntryLineList',
],

# https://webservices.netsuite.com/xsd/lists/v2019_2_0/employees.xsd
'ns34': [
'EmployeeSearch',
],

# urn:employees_2019_2.transactions.webservices.netsuite.com
# https://webservices.netsuite.com/xsd/transactions/v2019_2_0/employees.xsd
'ns38': [
'ExpenseReport',
'ExpenseReportExpense',
'ExpenseReportExpenseList',
],
}

SIMPLE_TYPES = {
Expand Down
97 changes: 97 additions & 0 deletions test/integration/data/expense_reports/tstdrv2089588.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"nullFieldList": null,
"createdDate": null,
"lastModifiedDate": null,
"status": null,
"customForm": null,
"account": {
"name": null,
"internalId": "25",
"externalId": null,
"type": "account"
},
"entity": {
"name": null,
"internalId": "1648",
"externalId": null,
"type": "vendor"
},
"expenseReportCurrency": {
"name": "USD",
"internalId": null,
"externalId": null,
"type": "currency"
},
"expenseReportExchangeRate": null,
"subsidiary": {
"name": null,
"internalId": "1",
"externalId": null,
"type": "subsidiary"
},
"taxPointDate": null,
"tranId": null,
"acctCorpCardExp": null,
"postingPeriod": null,
"tranDate": null,
"dueDate": null,
"approvalStatus": null,
"total": null,
"nextApprover": null,
"advance": null,
"tax1Amt": null,
"amount": null,
"memo": "Testing ExpenseReport using Fyle SDK",
"complete": null,
"supervisorApproval": null,
"accountingApproval": null,
"useMultiCurrency": null,
"tax2Amt": null,
"department": null,
"class": null,
"location": null,
"expenseList": [
{
"amount": 100,
"category": {
"name": null,
"internalId": "2",
"externalId": null,
"type": "account"
},
"class": null,
"corporateCreditCard": null,
"currency": {
"name": "USD",
"internalId": "1",
"externalId": null,
"type": "currency"
},
"customer": null,
"customFieldList": null,
"department": null,
"exchangeRate": null,
"expenseDate": null,
"expMediaItem": null,
"foreignAmount": null,
"grossAmt": null,
"isBillable": null,
"isNonReimbursable": null,
"line": null,
"location": null,
"memo": "Testing ExpenseReports using Fyle SDK",
"quantity": null,
"rate": null,
"receipt": null,
"refNumber": null,
"tax1Amt": null,
"taxCode": null,
"taxRate1": null,
"taxRate2":null
}
],
"accountingBookDetailList": null,
"customFieldList": null,
"internalId": null,
"externalId": "EXPR_1"
}
36 changes: 36 additions & 0 deletions test/integration/test_expense_reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import logging
import pytest
import json
import os

logger = logging.getLogger(__name__)

def test_get(nc):
data = next(nc.expense_reports.get_all_generator())
logger.debug('data = %s', data)
assert data, 'get all generator didnt work'
assert data['externalId'] == 'entity-5', f'No object found with externalId'
assert data['internalId'] == '-5', f'No object found with internalId'

data = nc.expense_reports.get(externalId='EXPR_1')
logger.debug('data = %s', data)
assert data, f'No object with externalId'
assert data['externalId'] == 'EXPR_1', f'No object with externalId'
assert data['internalId'] == '10613', f'No object with internalId'

def test_post(nc):
filename = os.getenv('NS_ACCOUNT').lower() + '.json'
with open('./test/integration/data/expense_reports/' + filename) as oj:
s = oj.read()
expr1 = json.loads(s)
logger.debug('expr1 = %s', expr1)
res = nc.expense_reports.post(expr1)
logger.debug('res = %s', res)
assert res['externalId'] == expr1['externalId'], 'External ID does not match'
assert res['type'] == 'expenseReport', 'Type does not match'

expr2 = nc.expense_reports.get(externalId=res['externalId'])
logger.debug('expr2 = %s', expr2)
assert expr2['amount'] == 100.0, 'Amount does not match'
assert expr2['externalId'] == 'EXPR_1', 'External ID does not match'
assert expr2['internalId'] == '10613', 'Internal ID does not match'
8 changes: 8 additions & 0 deletions test/internal/test_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ def test_get_journal_entry(ns):
record = ns.get(recordType='journalEntry', externalId='JE_01')
assert record, 'No journal entry found'

def test_get_employee(ns):
record = ns.get(recordType='employee', internalId='1648')
assert record, 'No employee record for internalId 1'

def test_get_expense_report(ns):
record = ns.get(recordType='ExpenseReport', externalId='EXPR_1')
assert record, 'No expense report found'

# def test_get_currency1(nc):
# currency = nc.currency.get(internal_id='1')
# logger.info('currency is %s', currency)
2 changes: 1 addition & 1 deletion test/internal/test_get_all.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def test_get_all(ns, type_name):
records = ns.getAll(recordType=type_name)
assert len(records) > 0, f'No records of type {type_name} returned'

@pytest.mark.parametrize('type_name', ['account', 'vendor', 'department', 'location', 'classification', 'subsidiaries'])
@pytest.mark.parametrize('type_name', ['account', 'vendor', 'department', 'location', 'classification', 'subsidiaries', 'employees'])
def test_get_all_not_supported(ns, type_name):
with pytest.raises(zeep.exceptions.Fault) as ex:
records = ns.getAll(recordType=type_name)
Expand Down
12 changes: 11 additions & 1 deletion test/internal/test_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ def test_search_journal_entries(ns):
assert len(paginated_search.records) > 0, 'There are no journal entries'
logger.debug('record = %s', str(paginated_search.records[0]))

@pytest.mark.parametrize('type_name', ['Account', 'Vendor', 'Department', 'Location', 'Classification'])
def test_search_expense_reports(ns):
record_type_search_field = ns.SearchStringField(searchValue='ExpenseReport', operator='contains')
basic_search = ns.basic_search_factory('Transaction', recordType=record_type_search_field)
paginated_search = PaginatedSearch(client=ns,
type_name='Transaction',
basic_search=basic_search,
pageSize=5)
assert len(paginated_search.records) > 0, 'There are no expense reports'
logger.debug('record = %s', str(paginated_search.records[0]))

@pytest.mark.parametrize('type_name', ['Account', 'Vendor', 'Department', 'Location', 'Classification', 'Subsidiary', 'Employee'])
def test_search_all(ns, type_name):
paginated_search = PaginatedSearch(client=ns, type_name=type_name, pageSize=20)
assert len(paginated_search.records) > 0, f'There are no records of type {type_name}'
Expand Down
38 changes: 38 additions & 0 deletions test/internal/test_upsert.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def get_category_account(ns):
def get_currency(ns):
return ns.get(recordType='currency', internalId='1')

def get_employee(ns):
return ns.get(recordType='employee', internalId='1648')

def test_upsert_vendor_bill(ns):
vendor_ref = ns.RecordRef(type='vendor', internalId=get_vendor(ns).internalId)
bill_account_ref = ns.RecordRef(type='account', internalId=25)
Expand Down Expand Up @@ -113,3 +116,38 @@ def test_upsert_journal_entry(ns):
je = ns.get(recordType='journalEntry', externalId='JE_1234')
logger.debug('je = %s', str(je))
assert (je['externalId'] == 'JE_1234'), 'Journal Entry External ID does not match'


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)
cat_account_ref = ns.RecordRef(type='account', internalId='1')
loc_ref = ns.RecordRef(type='location', internalId=get_location(ns).internalId)
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

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)
record_ref = ns.upsert(expense_report)
logger.debug('record_ref = %s', record_ref)
assert record_ref['externalId'] == 'EXPR_1', 'External ID does not match'

expr = ns.get(recordType='ExpenseReport', externalId='EXPR_1')
logger.debug('expense report = %s', str(expr))

0 comments on commit 2057f94

Please sign in to comment.