Skip to content

Commit

Permalink
Merge branch 'master' into 0.9.1
Browse files Browse the repository at this point in the history
  • Loading branch information
ej2 authored Nov 30, 2021
2 parents 4b2a47d + 2582c5b commit e59931b
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 58 deletions.
23 changes: 16 additions & 7 deletions quickbooks/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class QuickBooks(object):
sandbox = False
minorversion = None
verifier_token = None
invoice_link = False

sandbox_api_url_v3 = "https://sandbox-quickbooks.api.intuit.com/v3"
api_url_v3 = "https://quickbooks.api.intuit.com/v3"
Expand Down Expand Up @@ -88,6 +89,8 @@ def __new__(cls, **kwargs):
if 'minorversion' in kwargs:
instance.minorversion = kwargs['minorversion']

instance.invoice_link = kwargs.get('invoice_link', False)

if 'verifier_token' in kwargs:
instance.verifier_token = kwargs.get('verifier_token')

Expand Down Expand Up @@ -168,12 +171,18 @@ def change_data_capture(self, entity_string, changed_since):
return result

def make_request(self, request_type, url, request_body=None, content_type='application/json',
params=None, file_path=None):
params=None, file_path=None, request_id=None):
if not params:
params = {}

if self.minorversion:
params['minorversion'] = self.minorversion

if request_id:
params['requestid'] = request_id

if self.invoice_link:
params['include'] = 'invoiceLink'

if not request_body:
request_body = {}
Expand Down Expand Up @@ -296,11 +305,11 @@ def handle_exceptions(results):
else:
raise exceptions.QuickbooksException(message, code, detail)

def create_object(self, qbbo, request_body, _file_path=None):
def create_object(self, qbbo, request_body, _file_path=None, request_id=None):
self.isvalid_object_name(qbbo)

url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
results = self.post(url, request_body, file_path=_file_path)
results = self.post(url, request_body, file_path=_file_path, request_id=request_id)

return results

Expand All @@ -316,15 +325,15 @@ def isvalid_object_name(self, object_name):

return True

def update_object(self, qbbo, request_body, _file_path=None):
def update_object(self, qbbo, request_body, _file_path=None, request_id=None):
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
result = self.post(url, request_body, file_path=_file_path)
result = self.post(url, request_body, file_path=_file_path, request_id=request_id)

return result

def delete_object(self, qbbo, request_body, _file_path=None):
def delete_object(self, qbbo, request_body, _file_path=None, request_id=None):
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
result = self.post(url, request_body, params={'operation': 'delete'}, file_path=_file_path)
result = self.post(url, request_body, params={'operation': 'delete'}, file_path=_file_path, request_id=request_id)

return result

Expand Down
15 changes: 10 additions & 5 deletions quickbooks/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,38 @@ class UnsupportedException(QuickbooksException):
"""
Quickbooks Error Codes from 500 to 599
"""
pass
def __str__(self):
return "QB Unsupported Exception: " + self.message + " \n\n" + self.detail


class GeneralException(QuickbooksException):
"""
Quickbooks Error Codes from 600 to 1999
"""
pass
def __str__(self):
return "QB General Exception: " + self.message + " \n\n" + self.detail


class ValidationException(QuickbooksException):
"""
Quickbooks Error Codes from 2000 to 4999
"""
pass
def __str__(self):
return "QB Validation Exception: " + self.message + " \n\n" + self.detail


class SevereException(QuickbooksException):
"""
Quickbooks Error Codes greater than 10000
"""
pass
def __str__(self):
return "QB Severe Exception: " + self.message + " \n\n" + self.detail


class ObjectNotFoundException(QuickbooksException):
"""
Quickbooks Error Code 610
"""
pass
def __str__(self):
return "QB Object Not Found Exception: " + self.message + " \n\n" + self.detail

14 changes: 7 additions & 7 deletions quickbooks/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,14 @@ class UpdateMixin(object):
qbo_object_name = ""
qbo_json_object_name = ""

def save(self, qb=None):
def save(self, qb=None, request_id=None):
if not qb:
qb = QuickBooks()

if self.Id and int(self.Id) > 0:
json_data = qb.update_object(self.qbo_object_name, self.to_json())
json_data = qb.update_object(self.qbo_object_name, self.to_json(), request_id=request_id)
else:
json_data = qb.create_object(self.qbo_object_name, self.to_json())
json_data = qb.create_object(self.qbo_object_name, self.to_json(), request_id=request_id)

if self.qbo_json_object_name != '':
obj = type(self).from_json(json_data[self.qbo_json_object_name])
Expand All @@ -170,19 +170,19 @@ class UpdateNoIdMixin(object):
qbo_object_name = ""
qbo_json_object_name = ""

def save(self, qb=None):
def save(self, qb=None, request_id=None):
if not qb:
qb = QuickBooks()

json_data = qb.update_object(self.qbo_object_name, self.to_json())
json_data = qb.update_object(self.qbo_object_name, self.to_json(), request_id=request_id)
obj = type(self).from_json(json_data[self.qbo_object_name])
return obj


class DeleteMixin(object):
qbo_object_name = ""

def delete(self, qb=None):
def delete(self, qb=None, request_id=None):
if not qb:
qb = QuickBooks()

Expand All @@ -193,7 +193,7 @@ def delete(self, qb=None):
'Id': self.Id,
'SyncToken': self.SyncToken,
}
return qb.delete_object(self.qbo_object_name, json.dumps(data))
return qb.delete_object(self.qbo_object_name, json.dumps(data), request_id=request_id)


class ListMixin(object):
Expand Down
21 changes: 21 additions & 0 deletions quickbooks/objects/payment.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from six import python_2_unicode_compatible
from .base import QuickbooksBaseObject, Ref, LinkedTxn, \
QuickbooksManagedObject, QuickbooksTransactionEntity
from ..client import QuickBooks
from .creditcardpayment import CreditCardPayment
from ..mixins import DeleteMixin
import json


@python_2_unicode_compatible
Expand Down Expand Up @@ -77,5 +79,24 @@ def __init__(self):
# These fields are for minor version 4
self.TransactionLocationType = None

def void(self, qb=None):
if not qb:
qb = QuickBooks()

if not self.Id:
raise qb.QuickbooksException('Cannot void unsaved object')

data = {
'Id': self.Id,
'SyncToken': self.SyncToken,
'sparse': True
}

endpoint = self.qbo_object_name.lower()
url = "{0}/company/{1}/{2}".format(qb.api_url, qb.company_id, endpoint)
results = qb.post(url, json.dumps(data), params={'operation': 'update', 'include': 'void'})

return results

def __str__(self):
return str(self.TotalAmt)
70 changes: 33 additions & 37 deletions tests/integration/test_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,29 @@
from quickbooks.objects.invoice import Invoice
from quickbooks.objects.item import Item
from tests.integration.test_base import QuickbooksTestCase

import uuid

class InvoiceTest(QuickbooksTestCase):
def create_invoice(self, customer, request_id=None):
invoice = Invoice()

line = SalesItemLine()
line.LineNum = 1
line.Description = "description"
line.Amount = 100
line.SalesItemLineDetail = SalesItemLineDetail()
item = Item.all(max_results=1, qb=self.qb_client)[0]

line.SalesItemLineDetail.ItemRef = item.to_ref()
invoice.Line.append(line)

invoice.CustomerRef = customer.to_ref()

invoice.CustomerMemo = CustomerMemo()
invoice.CustomerMemo.value = "Customer Memo"
invoice.save(qb=self.qb_client, request_id=request_id)
return invoice

def test_query_by_customer_ref(self):
customer = Customer.all(max_results=1, qb=self.qb_client)[0]
invoice = Invoice.query(
Expand All @@ -26,52 +46,28 @@ def test_where(self):
self.assertEquals(invoice[0].CustomerRef.name, customer.DisplayName)

def test_create(self):
invoice = Invoice()

line = SalesItemLine()
line.LineNum = 1
line.Description = "description"
line.Amount = 100
line.SalesItemLineDetail = SalesItemLineDetail()
item = Item.all(max_results=1, qb=self.qb_client)[0]

line.SalesItemLineDetail.ItemRef = item.to_ref()
invoice.Line.append(line)

customer = Customer.all(max_results=1, qb=self.qb_client)[0]
invoice.CustomerRef = customer.to_ref()

invoice.CustomerMemo = CustomerMemo()
invoice.CustomerMemo.value = "Customer Memo"
invoice.save(qb=self.qb_client)

invoice = self.create_invoice(customer)
query_invoice = Invoice.get(invoice.Id, qb=self.qb_client)

self.assertEquals(query_invoice.CustomerRef.name, customer.DisplayName)
self.assertEquals(query_invoice.CustomerMemo.value, "Customer Memo")
self.assertEquals(query_invoice.Line[0].Description, "description")
self.assertEquals(query_invoice.Line[0].Amount, 100.0)

def test_create_idempotence(self):
customer = Customer.all(max_results=1, qb=self.qb_client)[0]
sample_request_id = str(uuid.uuid4())
invoice = self.create_invoice(customer, request_id=sample_request_id)
duplicate_invoice = self.create_invoice(customer, request_id=sample_request_id)

def test_delete(self):
# First create an invoice
invoice = Invoice()

line = SalesItemLine()
line.LineNum = 1
line.Description = "description"
line.Amount = 100
line.SalesItemLineDetail = SalesItemLineDetail()
item = Item.all(max_results=1, qb=self.qb_client)[0]

line.SalesItemLineDetail.ItemRef = item.to_ref()
invoice.Line.append(line)
# Assert that both returned invoices have the same id
self.assertEquals(invoice.Id, duplicate_invoice.Id)

def test_delete(self):
customer = Customer.all(max_results=1, qb=self.qb_client)[0]
invoice.CustomerRef = customer.to_ref()

invoice.CustomerMemo = CustomerMemo()
invoice.CustomerMemo.value = "Customer Memo"
invoice.save(qb=self.qb_client)
# First create an invoice
invoice = self.create_invoice(customer)

# Then delete
invoice_id = invoice.Id
Expand Down
21 changes: 21 additions & 0 deletions tests/integration/test_payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,24 @@ def test_create(self):
self.assertEqual(query_payment.CustomerRef.name, customer.DisplayName)
self.assertEqual(query_payment.TotalAmt, 140.0)
self.assertEqual(query_payment.PaymentMethodRef.value, payment_method.Id)

def test_void(self):
payment = Payment()
payment.TotalAmt = 100.0

customer = Customer.all(max_results=1, qb=self.qb_client)[0]
payment.CustomerRef = customer.to_ref()

payment_method = PaymentMethod.all(max_results=1, qb=self.qb_client)[0]

payment.PaymentMethodRef = payment_method.to_ref()
payment.save(qb=self.qb_client)

query_payment = Payment.get(payment.Id, qb=self.qb_client)
self.assertEqual(query_payment.TotalAmt, 100.0)

payment.void(qb=self.qb_client)
query_payment = Payment.get(payment.Id, qb=self.qb_client)

self.assertEqual(query_payment.TotalAmt, 0.0)
self.assertIn('Voided', query_payment.PrivateNote)
10 changes: 10 additions & 0 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def test_update_object(self, post):

self.assertTrue(post.called)

@patch('quickbooks.client.QuickBooks.make_request')
def test_update_object_with_request_id(self, make_req):
qb_client = client.QuickBooks()
qb_client.company_id = "1234"
qb_client.update_object("Customer", "request_body", request_id="123")

url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/customer"
make_req.assert_called_with("POST", url, "request_body", file_path = None, request_id="123")

@patch('quickbooks.client.QuickBooks.get')
def test_get_current_user(self, get):
qb_client = client.QuickBooks()
Expand Down Expand Up @@ -163,6 +172,7 @@ def test_make_request(self, process_request):
"GET", url, data={},
headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-quickbooks V3 library'}, params={})


def test_handle_exceptions(self):
qb_client = client.QuickBooks()
error_data = {
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class UpdateMixinTest(QuickbooksUnitTestCase):
def test_save_create(self, create_object):
department = Department()
department.save(qb=self.qb_client)
create_object.assert_called_once_with("Department", department.to_json())
create_object.assert_called_once_with("Department", department.to_json(), request_id=None)

def test_save_create_with_qb(self):
with patch.object(self.qb_client, 'create_object') as create_object:
Expand All @@ -242,7 +242,7 @@ def test_save_update(self, update_object):
json = department.to_json()

department.save(qb=self.qb_client)
update_object.assert_called_once_with("Department", json)
update_object.assert_called_once_with("Department", json, request_id=None)

def test_save_update_with_qb(self):
with patch.object(self.qb_client, 'update_object') as update_object:
Expand Down

0 comments on commit e59931b

Please sign in to comment.