diff --git a/quickbooks/client.py b/quickbooks/client.py index e8799907..6ee3780c 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -167,12 +167,15 @@ 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' @@ -297,11 +300,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 @@ -317,15 +320,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 diff --git a/quickbooks/mixins.py b/quickbooks/mixins.py index 2738ad5b..0cda656c 100644 --- a/quickbooks/mixins.py +++ b/quickbooks/mixins.py @@ -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]) @@ -170,11 +170,11 @@ 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 @@ -182,7 +182,7 @@ def save(self, qb=None): class DeleteMixin(object): qbo_object_name = "" - def delete(self, qb=None): + def delete(self, qb=None, request_id=None): if not qb: qb = QuickBooks() @@ -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): diff --git a/tests/integration/test_invoice.py b/tests/integration/test_invoice.py index e0b5a197..8e05dd56 100644 --- a/tests/integration/test_invoice.py +++ b/tests/integration/test_invoice.py @@ -4,10 +4,11 @@ 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 test_create(self): + + def create_invoice(self, customer, request_id=None): invoice = Invoice() line = SalesItemLine() @@ -20,40 +21,36 @@ def test_create(self): 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.save(qb=self.qb_client, request_id=request_id) + return invoice + def test_create(self): + customer = Customer.all(max_results=1, qb=self.qb_client)[0] + 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 diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 69547366..3ec7f397 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -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() @@ -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 = { diff --git a/tests/unit/test_mixins.py b/tests/unit/test_mixins.py index 42765538..9a61afe1 100644 --- a/tests/unit/test_mixins.py +++ b/tests/unit/test_mixins.py @@ -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: @@ -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: