Skip to content

Add SKU to Item #379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion dev_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
coverage==7.3.0
ipdb==0.13.13
mock==5.1.0
nose==1.3.7
2 changes: 1 addition & 1 deletion quickbooks/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def process_request(self, request_type, url, headers="", params="", data=""):
request_type, url, headers=headers, params=params, data=data)

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

return result
Expand Down
28 changes: 20 additions & 8 deletions quickbooks/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,26 @@ class ListMixin(object):

@classmethod
def all(cls, order_by="", start_position="", max_results=100, qb=None):
"""
:param start_position:
:param max_results: The max number of entities that can be returned in a response is 1000.
:param qb:
:return: Returns list
"""
return cls.where("", order_by=order_by, start_position=start_position,
max_results=max_results, qb=qb)
"""Returns list of objects containing all objects in the QuickBooks database"""
if qb is None:
qb = QuickBooks()

# For Item objects, we need to explicitly request the SKU field
if cls.qbo_object_name == "Item":
select = "SELECT *, Sku FROM {0}".format(cls.qbo_object_name)
else:
select = "SELECT * FROM {0}".format(cls.qbo_object_name)

if order_by:
select += " ORDER BY {0}".format(order_by)

if start_position:
select += " STARTPOSITION {0}".format(start_position)

if max_results:
select += " MAXRESULTS {0}".format(max_results)

return cls.query(select, qb=qb)

@classmethod
def filter(cls, order_by="", start_position="", max_results="", qb=None, **kwargs):
Expand Down
77 changes: 61 additions & 16 deletions tests/integration/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,77 @@ def setUp(self):

def test_create(self):
account = Account()
account.AcctNum = self.account_number
account.Name = self.name
# Use shorter timestamp for uniqueness (within 20 char limit)
timestamp = datetime.now().strftime('%m%d%H%M%S')
unique_number = f"T{timestamp}" # T for Test
unique_name = f"Test Account {timestamp}"

account.AcctNum = unique_number
account.Name = unique_name
account.AccountType = "Bank" # Required field
account.AccountSubType = "CashOnHand"
account.save(qb=self.qb_client)

self.id = account.Id
query_account = Account.get(account.Id, qb=self.qb_client)
created_account = account.save(qb=self.qb_client)

# Verify the save was successful
self.assertIsNotNone(created_account)
self.assertIsNotNone(created_account.Id)
self.assertTrue(int(created_account.Id) > 0)

self.assertEqual(account.Id, query_account.Id)
self.assertEqual(query_account.Name, self.name)
self.assertEqual(query_account.AcctNum, self.account_number)
query_account = Account.get(created_account.Id, qb=self.qb_client)

self.assertEqual(created_account.Id, query_account.Id)
self.assertEqual(query_account.Name, unique_name)
self.assertEqual(query_account.AcctNum, unique_number)
self.assertEqual(query_account.AccountType, "Bank")
self.assertEqual(query_account.AccountSubType, "CashOnHand")

def test_update(self):
account = Account.filter(Name=self.name, qb=self.qb_client)[0]
# First create an account with a unique name and number
timestamp = datetime.now().strftime('%m%d%H%M%S')
unique_number = f"T{timestamp}"
unique_name = f"Test Account {timestamp}"

account = Account()
account.AcctNum = unique_number
account.Name = unique_name
account.AccountType = "Bank"
account.AccountSubType = "CashOnHand"

account.Name = "Updated Name {0}".format(self.account_number)
account.save(qb=self.qb_client)
created_account = account.save(qb=self.qb_client)

# Verify the save was successful
self.assertIsNotNone(created_account)
self.assertIsNotNone(created_account.Id)

query_account = Account.get(account.Id, qb=self.qb_client)
self.assertEqual(query_account.Name, "Updated Name {0}".format(self.account_number))
# Change the name
updated_name = f"{unique_name}_updated"
created_account.Name = updated_name
updated_account = created_account.save(qb=self.qb_client)

# Query the account and make sure it has changed
query_account = Account.get(updated_account.Id, qb=self.qb_client)
self.assertEqual(query_account.Name, updated_name)
self.assertEqual(query_account.AcctNum, unique_number) # Account number should not change

def test_create_using_from_json(self):
timestamp = datetime.now().strftime('%m%d%H%M%S')
unique_number = f"T{timestamp}"
unique_name = f"Test JSON {timestamp}"

account = Account.from_json({
"AcctNum": datetime.now().strftime('%d%H%M%S'),
"Name": "{} {}".format(self.name, self.time.strftime("%Y-%m-%d %H:%M:%S")),
"AcctNum": unique_number,
"Name": unique_name,
"AccountType": "Bank",
"AccountSubType": "CashOnHand"
})

account.save(qb=self.qb_client)
created_account = account.save(qb=self.qb_client)
self.assertIsNotNone(created_account)
self.assertIsNotNone(created_account.Id)

# Verify we can get the account
query_account = Account.get(created_account.Id, qb=self.qb_client)
self.assertEqual(query_account.Name, unique_name)
self.assertEqual(query_account.AccountType, "Bank")
self.assertEqual(query_account.AccountSubType, "CashOnHand")
1 change: 0 additions & 1 deletion tests/integration/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def setUp(self):
)

self.qb_client = QuickBooks(
minorversion=73,
auth_client=self.auth_client,
refresh_token=os.environ.get('REFRESH_TOKEN'),
company_id=os.environ.get('COMPANY_ID'),
Expand Down
19 changes: 19 additions & 0 deletions tests/integration/test_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,22 @@ def test_create(self):
self.assertEqual(query_item.IncomeAccountRef.value, self.income_account.Id)
self.assertEqual(query_item.ExpenseAccountRef.value, self.expense_account.Id)
self.assertEqual(query_item.AssetAccountRef.value, self.asset_account.Id)

def test_sku_in_all(self):
"""Test that SKU is properly returned when using Item.all()"""
# First create an item with a SKU
unique_name = "Test SKU Item {0}".format(datetime.now().strftime('%d%H%M%S'))
item = Item()
item.Name = unique_name
item.Type = "Service"
item.Sku = "TEST_SKU_" + self.account_number
item.IncomeAccountRef = self.income_account.to_ref()
item.ExpenseAccountRef = self.expense_account.to_ref()
item.save(qb=self.qb_client)

# Now fetch all items and verify the SKU is present
items = Item.all(max_results=100, qb=self.qb_client)
found_item = next((i for i in items if i.Id == item.Id), None)

self.assertIsNotNone(found_item, "Created item not found in Item.all() results")
self.assertEqual(found_item.Sku, "TEST_SKU_" + self.account_number)
5 changes: 1 addition & 4 deletions tests/unit/test_batch.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import unittest
try:
from mock import patch
except ImportError:
from unittest.mock import patch
from unittest.mock import patch
from quickbooks import batch, client
from quickbooks.objects.customer import Customer
from quickbooks.exceptions import QuickbooksException
Expand Down
5 changes: 1 addition & 4 deletions tests/unit/test_cdc.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import unittest
try:
from mock import patch
except ImportError:
from unittest.mock import patch
from unittest.mock import patch
from quickbooks.cdc import change_data_capture
from quickbooks.objects import Invoice, Customer
from quickbooks import QuickBooks
Expand Down
23 changes: 9 additions & 14 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import json
import warnings
from tests.integration.test_base import QuickbooksUnitTestCase

try:
from mock import patch, mock_open
except ImportError:
from unittest.mock import patch, mock_open
from unittest.mock import patch, mock_open

from quickbooks.exceptions import QuickbooksException, SevereException, AuthorizationException
from quickbooks import client, mixins
Expand All @@ -32,12 +28,10 @@ def test_client_new(self):
self.qb_client = client.QuickBooks(
company_id="company_id",
verbose=True,
minorversion=75,
verifier_token=TEST_VERIFIER_TOKEN,
)

self.assertEqual(self.qb_client.company_id, "company_id")
self.assertEqual(self.qb_client.minorversion, 75)

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

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

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

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

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

@property
def status_code(self):
Expand All @@ -275,10 +269,8 @@ def status_code(self):
return httplib.OK

def json(self):
return "{}"
return json.loads(self.text)

def content(self):
return ''

class MockResponseJson:
def __init__(self, json_data=None, status_code=200):
Expand Down Expand Up @@ -327,5 +319,8 @@ def get_session(self):


class MockSession(object):
def request(self, request_type, url, no_idea, company_id, **kwargs):
def __init__(self):
self.access_token = "test_access_token"

def request(self, request_type, url, headers=None, params=None, data=None, **kwargs):
return MockResponse()
19 changes: 10 additions & 9 deletions tests/unit/test_mixins.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import unittest
from urllib.parse import quote
from unittest import TestCase
from datetime import datetime
from unittest.mock import patch, ANY

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

from tests.integration.test_base import QuickbooksUnitTestCase

try:
from mock import patch
except ImportError:
from unittest.mock import patch
from tests.unit.test_client import MockSession

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


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

def test_all_with_qb(self):
self.qb_client.session = MockSession() # Add a mock session
with patch.object(self.qb_client, 'query') as query:
Department.all(qb=self.qb_client)
self.assertTrue(query.called)
query.assert_called_once()

@patch('quickbooks.mixins.ListMixin.where')
def test_filter(self, where):
Expand Down