Skip to content

Commit 39abbef

Browse files
authored
Merge pull request #379 from laf-rge/sku-fix
Add SKU to Item
2 parents 04a0cd4 + 67e94c2 commit 39abbef

File tree

10 files changed

+122
-58
lines changed

10 files changed

+122
-58
lines changed

dev_requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
coverage==7.3.0
22
ipdb==0.13.13
3-
mock==5.1.0
43
nose==1.3.7

quickbooks/client.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ def process_request(self, request_type, url, headers="", params="", data=""):
248248
request_type, url, headers=headers, params=params, data=data)
249249

250250
def get_single_object(self, qbbo, pk, params=None):
251-
url = "{0}/company/{1}/{2}/{3}/".format(self.api_url, self.company_id, qbbo.lower(), pk)
251+
url = "{0}/company/{1}/{2}/{3}".format(self.api_url, self.company_id, qbbo.lower(), pk)
252252
result = self.get(url, {}, params=params)
253253

254254
return result

quickbooks/mixins.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -255,14 +255,26 @@ class ListMixin(object):
255255

256256
@classmethod
257257
def all(cls, order_by="", start_position="", max_results=100, qb=None):
258-
"""
259-
:param start_position:
260-
:param max_results: The max number of entities that can be returned in a response is 1000.
261-
:param qb:
262-
:return: Returns list
263-
"""
264-
return cls.where("", order_by=order_by, start_position=start_position,
265-
max_results=max_results, qb=qb)
258+
"""Returns list of objects containing all objects in the QuickBooks database"""
259+
if qb is None:
260+
qb = QuickBooks()
261+
262+
# For Item objects, we need to explicitly request the SKU field
263+
if cls.qbo_object_name == "Item":
264+
select = "SELECT *, Sku FROM {0}".format(cls.qbo_object_name)
265+
else:
266+
select = "SELECT * FROM {0}".format(cls.qbo_object_name)
267+
268+
if order_by:
269+
select += " ORDER BY {0}".format(order_by)
270+
271+
if start_position:
272+
select += " STARTPOSITION {0}".format(start_position)
273+
274+
if max_results:
275+
select += " MAXRESULTS {0}".format(max_results)
276+
277+
return cls.query(select, qb=qb)
266278

267279
@classmethod
268280
def filter(cls, order_by="", start_position="", max_results="", qb=None, **kwargs):

tests/integration/test_account.py

+61-16
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,77 @@ def setUp(self):
1313

1414
def test_create(self):
1515
account = Account()
16-
account.AcctNum = self.account_number
17-
account.Name = self.name
16+
# Use shorter timestamp for uniqueness (within 20 char limit)
17+
timestamp = datetime.now().strftime('%m%d%H%M%S')
18+
unique_number = f"T{timestamp}" # T for Test
19+
unique_name = f"Test Account {timestamp}"
20+
21+
account.AcctNum = unique_number
22+
account.Name = unique_name
23+
account.AccountType = "Bank" # Required field
1824
account.AccountSubType = "CashOnHand"
19-
account.save(qb=self.qb_client)
2025

21-
self.id = account.Id
22-
query_account = Account.get(account.Id, qb=self.qb_client)
26+
created_account = account.save(qb=self.qb_client)
27+
28+
# Verify the save was successful
29+
self.assertIsNotNone(created_account)
30+
self.assertIsNotNone(created_account.Id)
31+
self.assertTrue(int(created_account.Id) > 0)
2332

24-
self.assertEqual(account.Id, query_account.Id)
25-
self.assertEqual(query_account.Name, self.name)
26-
self.assertEqual(query_account.AcctNum, self.account_number)
33+
query_account = Account.get(created_account.Id, qb=self.qb_client)
34+
35+
self.assertEqual(created_account.Id, query_account.Id)
36+
self.assertEqual(query_account.Name, unique_name)
37+
self.assertEqual(query_account.AcctNum, unique_number)
38+
self.assertEqual(query_account.AccountType, "Bank")
39+
self.assertEqual(query_account.AccountSubType, "CashOnHand")
2740

2841
def test_update(self):
29-
account = Account.filter(Name=self.name, qb=self.qb_client)[0]
42+
# First create an account with a unique name and number
43+
timestamp = datetime.now().strftime('%m%d%H%M%S')
44+
unique_number = f"T{timestamp}"
45+
unique_name = f"Test Account {timestamp}"
46+
47+
account = Account()
48+
account.AcctNum = unique_number
49+
account.Name = unique_name
50+
account.AccountType = "Bank"
51+
account.AccountSubType = "CashOnHand"
3052

31-
account.Name = "Updated Name {0}".format(self.account_number)
32-
account.save(qb=self.qb_client)
53+
created_account = account.save(qb=self.qb_client)
54+
55+
# Verify the save was successful
56+
self.assertIsNotNone(created_account)
57+
self.assertIsNotNone(created_account.Id)
3358

34-
query_account = Account.get(account.Id, qb=self.qb_client)
35-
self.assertEqual(query_account.Name, "Updated Name {0}".format(self.account_number))
59+
# Change the name
60+
updated_name = f"{unique_name}_updated"
61+
created_account.Name = updated_name
62+
updated_account = created_account.save(qb=self.qb_client)
63+
64+
# Query the account and make sure it has changed
65+
query_account = Account.get(updated_account.Id, qb=self.qb_client)
66+
self.assertEqual(query_account.Name, updated_name)
67+
self.assertEqual(query_account.AcctNum, unique_number) # Account number should not change
3668

3769
def test_create_using_from_json(self):
70+
timestamp = datetime.now().strftime('%m%d%H%M%S')
71+
unique_number = f"T{timestamp}"
72+
unique_name = f"Test JSON {timestamp}"
73+
3874
account = Account.from_json({
39-
"AcctNum": datetime.now().strftime('%d%H%M%S'),
40-
"Name": "{} {}".format(self.name, self.time.strftime("%Y-%m-%d %H:%M:%S")),
75+
"AcctNum": unique_number,
76+
"Name": unique_name,
77+
"AccountType": "Bank",
4178
"AccountSubType": "CashOnHand"
4279
})
4380

44-
account.save(qb=self.qb_client)
81+
created_account = account.save(qb=self.qb_client)
82+
self.assertIsNotNone(created_account)
83+
self.assertIsNotNone(created_account.Id)
84+
85+
# Verify we can get the account
86+
query_account = Account.get(created_account.Id, qb=self.qb_client)
87+
self.assertEqual(query_account.Name, unique_name)
88+
self.assertEqual(query_account.AccountType, "Bank")
89+
self.assertEqual(query_account.AccountSubType, "CashOnHand")

tests/integration/test_base.py

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ def setUp(self):
1717
)
1818

1919
self.qb_client = QuickBooks(
20-
minorversion=73,
2120
auth_client=self.auth_client,
2221
refresh_token=os.environ.get('REFRESH_TOKEN'),
2322
company_id=os.environ.get('COMPANY_ID'),

tests/integration/test_item.py

+19
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,22 @@ def test_create(self):
4545
self.assertEqual(query_item.IncomeAccountRef.value, self.income_account.Id)
4646
self.assertEqual(query_item.ExpenseAccountRef.value, self.expense_account.Id)
4747
self.assertEqual(query_item.AssetAccountRef.value, self.asset_account.Id)
48+
49+
def test_sku_in_all(self):
50+
"""Test that SKU is properly returned when using Item.all()"""
51+
# First create an item with a SKU
52+
unique_name = "Test SKU Item {0}".format(datetime.now().strftime('%d%H%M%S'))
53+
item = Item()
54+
item.Name = unique_name
55+
item.Type = "Service"
56+
item.Sku = "TEST_SKU_" + self.account_number
57+
item.IncomeAccountRef = self.income_account.to_ref()
58+
item.ExpenseAccountRef = self.expense_account.to_ref()
59+
item.save(qb=self.qb_client)
60+
61+
# Now fetch all items and verify the SKU is present
62+
items = Item.all(max_results=100, qb=self.qb_client)
63+
found_item = next((i for i in items if i.Id == item.Id), None)
64+
65+
self.assertIsNotNone(found_item, "Created item not found in Item.all() results")
66+
self.assertEqual(found_item.Sku, "TEST_SKU_" + self.account_number)

tests/unit/test_batch.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import unittest
2-
try:
3-
from mock import patch
4-
except ImportError:
5-
from unittest.mock import patch
2+
from unittest.mock import patch
63
from quickbooks import batch, client
74
from quickbooks.objects.customer import Customer
85
from quickbooks.exceptions import QuickbooksException

tests/unit/test_cdc.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import unittest
2-
try:
3-
from mock import patch
4-
except ImportError:
5-
from unittest.mock import patch
2+
from unittest.mock import patch
63
from quickbooks.cdc import change_data_capture
74
from quickbooks.objects import Invoice, Customer
85
from quickbooks import QuickBooks

tests/unit/test_client.py

+9-14
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import json
22
import warnings
33
from tests.integration.test_base import QuickbooksUnitTestCase
4-
5-
try:
6-
from mock import patch, mock_open
7-
except ImportError:
8-
from unittest.mock import patch, mock_open
4+
from unittest.mock import patch, mock_open
95

106
from quickbooks.exceptions import QuickbooksException, SevereException, AuthorizationException
117
from quickbooks import client, mixins
@@ -32,12 +28,10 @@ def test_client_new(self):
3228
self.qb_client = client.QuickBooks(
3329
company_id="company_id",
3430
verbose=True,
35-
minorversion=75,
3631
verifier_token=TEST_VERIFIER_TOKEN,
3732
)
3833

3934
self.assertEqual(self.qb_client.company_id, "company_id")
40-
self.assertEqual(self.qb_client.minorversion, 75)
4135

4236
def test_client_with_deprecated_minor_version(self):
4337
with warnings.catch_warnings(record=True) as w:
@@ -154,7 +148,7 @@ def test_get_single_object(self, make_req):
154148
qb_client.company_id = "1234"
155149

156150
qb_client.get_single_object("test", 1)
157-
url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/"
151+
url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1"
158152
make_req.assert_called_with("GET", url, {}, params=None)
159153

160154
@patch('quickbooks.client.QuickBooks.make_request')
@@ -163,7 +157,7 @@ def test_get_single_object_with_params(self, make_req):
163157
qb_client.company_id = "1234"
164158

165159
qb_client.get_single_object("test", 1, params={'param':'value'})
166-
url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/"
160+
url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1"
167161
make_req.assert_called_with("GET", url, {}, params={'param':'value'})
168162

169163
@patch('quickbooks.client.QuickBooks.process_request')
@@ -264,7 +258,7 @@ def test_make_request_file_closed(self, process_request):
264258
class MockResponse(object):
265259
@property
266260
def text(self):
267-
return "oauth_token_secret=secretvalue&oauth_callback_confirmed=true&oauth_token=tokenvalue"
261+
return '{"QueryResponse": {"Department": []}}'
268262

269263
@property
270264
def status_code(self):
@@ -275,10 +269,8 @@ def status_code(self):
275269
return httplib.OK
276270

277271
def json(self):
278-
return "{}"
272+
return json.loads(self.text)
279273

280-
def content(self):
281-
return ''
282274

283275
class MockResponseJson:
284276
def __init__(self, json_data=None, status_code=200):
@@ -327,5 +319,8 @@ def get_session(self):
327319

328320

329321
class MockSession(object):
330-
def request(self, request_type, url, no_idea, company_id, **kwargs):
322+
def __init__(self):
323+
self.access_token = "test_access_token"
324+
325+
def request(self, request_type, url, headers=None, params=None, data=None, **kwargs):
331326
return MockResponse()

tests/unit/test_mixins.py

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import unittest
22
from urllib.parse import quote
3+
from unittest import TestCase
4+
from datetime import datetime
5+
from unittest.mock import patch, ANY
36

47
from quickbooks.objects import Bill, Invoice, Payment, BillPayment
58

69
from tests.integration.test_base import QuickbooksUnitTestCase
7-
8-
try:
9-
from mock import patch
10-
except ImportError:
11-
from unittest.mock import patch
10+
from tests.unit.test_client import MockSession
1211

1312
from quickbooks.objects.base import PhoneNumber, QuickbooksBaseObject
1413
from quickbooks.objects.department import Department
@@ -130,15 +129,17 @@ def test_to_dict(self):
130129

131130

132131
class ListMixinTest(QuickbooksUnitTestCase):
133-
@patch('quickbooks.mixins.ListMixin.where')
134-
def test_all(self, where):
132+
@patch('quickbooks.mixins.ListMixin.query')
133+
def test_all(self, query):
134+
query.return_value = []
135135
Department.all()
136-
where.assert_called_once_with('', order_by='', max_results=100, start_position='', qb=None)
136+
query.assert_called_once_with("SELECT * FROM Department MAXRESULTS 100", qb=ANY)
137137

138138
def test_all_with_qb(self):
139+
self.qb_client.session = MockSession() # Add a mock session
139140
with patch.object(self.qb_client, 'query') as query:
140141
Department.all(qb=self.qb_client)
141-
self.assertTrue(query.called)
142+
query.assert_called_once()
142143

143144
@patch('quickbooks.mixins.ListMixin.where')
144145
def test_filter(self, where):

0 commit comments

Comments
 (0)