Skip to content

Commit 370cb0f

Browse files
authored
Merge pull request #293 from ej2/0.9.3
0.9.3 Release
2 parents 5acaa36 + 1b96b23 commit 370cb0f

81 files changed

Lines changed: 1183 additions & 675 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.travis.yml

Lines changed: 0 additions & 16 deletions
This file was deleted.

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
Changelog
22
=========
3+
* 0.9.3 (March 7, 2023)
4+
* Added support for Recurring Transaction
5+
* Added support for optional query params
6+
* Fixed errors in example code on the readme
7+
* Removed enable_global and disable_global
38

49
* 0.9.2 (August 3, 2022)
510
* Removed pycparser dependency

Pipfile.lock

Lines changed: 331 additions & 184 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
python-quickbooks
22
=================
33

4-
[![](https://travis-ci.org/ej2/python-quickbooks.svg?branch=master)](https://travis-ci.org/ej2/python-quickbooks)
4+
[![Python package](https://github.com/ej2/python-quickbooks/actions/workflows/python-package.yml/badge.svg)](https://github.com/ej2/python-quickbooks/actions/workflows/python-package.yml)
55
[![Coverage Status](https://coveralls.io/repos/github/ej2/python-quickbooks/badge.svg?branch=master)](https://coveralls.io/github/ej2/python-quickbooks?branch=master)
66
[![](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ej2/python-quickbooks/blob/master/LICENSE)
77

@@ -159,12 +159,12 @@ Batch update a list of objects:
159159

160160
results = batch_update(customers, qb=client)
161161

162-
Batch delete a list of objects:
162+
Batch delete a list of objects (only entities that support delete can use batch delete):
163163

164164
from quickbooks.batch import batch_delete
165165

166-
customers = Customer.filter(Active=False)
167-
results = batch_delete(customers, qb=client)
166+
payments = Payment.filter(TxnDate=date.today())
167+
results = batch_delete(payments, qb=client)
168168

169169
Review results for batch operation:
170170

@@ -233,22 +233,26 @@ Attaching a file to customer:
233233
attachment.ContentType = 'application/pdf'
234234
attachment.save(qb=client)
235235

236+
Passing in optional params
237+
----------------
238+
Some QBO objects have options that need to be set on the query string of an API call.
239+
One example is `include=allowduplicatedocnum` on the Purchase object. You can add these params when calling save:
240+
241+
purchase.save(qb=self.qb_client, params={'include': 'allowduplicatedocnum'})
242+
236243
Other operations
237244
----------------
245+
Add Sharable link for an invoice sent to external customers (minorversion must be set to 36 or greater):
246+
247+
invoice.invoice_link = true
248+
249+
238250
Void an invoice:
239251

240252
invoice = Invoice()
241253
invoice.Id = 7
242254
invoice.void(qb=client)
243255

244-
If your company_id never changes you can enable the client to stay running (deprecation warning: will be removed in next release):
245-
246-
QuickBooks.enable_global()
247-
248-
You can disable the global client like so (deprecation warning: will be removed in next release):
249-
250-
QuickBooks.disable_global()
251-
252256

253257
Working with JSON data
254258
----------------

quickbooks/client.py

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class QuickBooks(object):
5353
"Purchase", "PurchaseOrder", "RefundReceipt",
5454
"SalesReceipt", "TaxAgency", "TaxCode", "TaxService/Taxcode", "TaxRate", "Term",
5555
"TimeActivity", "Transfer", "Vendor", "VendorCredit", "CreditCardPayment",
56+
"RecurringTransaction"
5657
]
5758

5859
__instance = None
@@ -107,27 +108,6 @@ def _start_session(self):
107108
)
108109
return self.auth_client.refresh_token
109110

110-
@classmethod
111-
def get_instance(cls):
112-
return cls.__instance
113-
114-
@classmethod
115-
def disable_global(cls):
116-
"""
117-
Disable use of singleton pattern.
118-
"""
119-
warnings.warn("disable_global deprecated", PendingDeprecationWarning)
120-
QuickBooks.__use_global = False
121-
QuickBooks.__instance = None
122-
123-
@classmethod
124-
def enable_global(cls):
125-
"""
126-
Allow use of singleton pattern.
127-
"""
128-
warnings.warn("enable_global deprecated", PendingDeprecationWarning)
129-
QuickBooks.__use_global = True
130-
131111
def _drop(self):
132112
QuickBooks.__instance = None
133113

@@ -172,6 +152,7 @@ def change_data_capture(self, entity_string, changed_since):
172152

173153
def make_request(self, request_type, url, request_body=None, content_type='application/json',
174154
params=None, file_path=None, request_id=None):
155+
print(params)
175156
if not params:
176157
params = {}
177158

@@ -305,17 +286,17 @@ def handle_exceptions(results):
305286
else:
306287
raise exceptions.QuickbooksException(message, code, detail)
307288

308-
def create_object(self, qbbo, request_body, _file_path=None, request_id=None):
289+
def create_object(self, qbbo, request_body, _file_path=None, request_id=None, params=None):
309290
self.isvalid_object_name(qbbo)
310291

311292
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
312-
results = self.post(url, request_body, file_path=_file_path, request_id=request_id)
293+
results = self.post(url, request_body, file_path=_file_path, request_id=request_id, params=params)
313294

314295
return results
315296

316-
def query(self, select):
297+
def query(self, select, params=None):
317298
url = "{0}/company/{1}/query".format(self.api_url, self.company_id)
318-
result = self.post(url, select, content_type='application/text')
299+
result = self.post(url, select, content_type='application/text', params=params)
319300

320301
return result
321302

@@ -325,9 +306,9 @@ def isvalid_object_name(self, object_name):
325306

326307
return True
327308

328-
def update_object(self, qbbo, request_body, _file_path=None, request_id=None):
309+
def update_object(self, qbbo, request_body, _file_path=None, request_id=None, params=None):
329310
url = "{0}/company/{1}/{2}".format(self.api_url, self.company_id, qbbo.lower())
330-
result = self.post(url, request_body, file_path=_file_path, request_id=request_id)
311+
result = self.post(url, request_body, file_path=_file_path, request_id=request_id, params=params)
331312

332313
return result
333314

quickbooks/mixins.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,14 @@ class UpdateMixin(object):
148148
qbo_object_name = ""
149149
qbo_json_object_name = ""
150150

151-
def save(self, qb=None, request_id=None):
151+
def save(self, qb=None, request_id=None, params=None):
152152
if not qb:
153153
qb = QuickBooks()
154154

155155
if self.Id and int(self.Id) > 0:
156-
json_data = qb.update_object(self.qbo_object_name, self.to_json(), request_id=request_id)
156+
json_data = qb.update_object(self.qbo_object_name, self.to_json(), request_id=request_id, params=params)
157157
else:
158-
json_data = qb.create_object(self.qbo_object_name, self.to_json(), request_id=request_id)
158+
json_data = qb.create_object(self.qbo_object_name, self.to_json(), request_id=request_id, params=params)
159159

160160
if self.qbo_json_object_name != '':
161161
obj = type(self).from_json(json_data[self.qbo_json_object_name])
@@ -196,6 +196,16 @@ def delete(self, qb=None, request_id=None):
196196
return qb.delete_object(self.qbo_object_name, json.dumps(data), request_id=request_id)
197197

198198

199+
class DeleteNoIdMixin(object):
200+
qbo_object_name = ""
201+
202+
def delete(self, qb=None, request_id=None):
203+
if not qb:
204+
qb = QuickBooks()
205+
206+
return qb.delete_object(self.qbo_object_name, self.to_json(), request_id=request_id)
207+
208+
199209
class ListMixin(object):
200210
qbo_object_name = ""
201211
qbo_json_object_name = ""

quickbooks/objects/purchase.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Purchase(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity
2222
For example, to create a transaction that sends a check to a vendor, create a Purchase object with PaymentType
2323
set to Check. To query Purchase transactions of a certain type, for example Check, submit the following to the
2424
query endpoint: SELECT * from Purchase where PaymentType='Check' You must specify an AccountRef for all purchases.
25-
The TotalAmtattribute must add up to sum of Line.Amount attributes.
25+
The TotalAmt attribute must add up to sum of Line.Amount attributes.
2626
"""
2727
class_dict = {
2828
"AccountRef": Ref,
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from six import python_2_unicode_compatible
2+
3+
from .bill import Bill
4+
from .creditmemo import CreditMemo
5+
from .deposit import Deposit
6+
from .estimate import Estimate
7+
from .invoice import Invoice
8+
from .journalentry import JournalEntry
9+
from .purchase import Purchase
10+
from .purchaseorder import PurchaseOrder
11+
from .refundreceipt import RefundReceipt
12+
from .salesreceipt import SalesReceipt
13+
from .transfer import Transfer
14+
from .vendorcredit import VendorCredit
15+
from .base import Ref, QuickbooksBaseObject
16+
from ..mixins import UpdateNoIdMixin, ListMixin, ReadMixin, DeleteNoIdMixin
17+
18+
class ScheduleInfo(QuickbooksBaseObject):
19+
def __init__(self):
20+
super(ScheduleInfo, self).__init__()
21+
22+
self.StartDate = None
23+
self.EndDate = None
24+
self.DaysBefore = None
25+
self.MaxOccurrences = None
26+
27+
self.RemindDays = None
28+
self.IntervalType = None
29+
self.NumInterval = None
30+
31+
self.DayOfMonth = None
32+
self.DayOfWeek = None
33+
self.MonthOfYear = None
34+
self.WeekOfMonth = None
35+
36+
self.NextDate = None
37+
self.PreviousDate = None
38+
39+
40+
class RecurringInfo(QuickbooksBaseObject):
41+
class_dict = {
42+
"ScheduleInfo": ScheduleInfo
43+
}
44+
45+
qbo_object_name = "RecurringInfo"
46+
47+
def __init__(self):
48+
super(RecurringInfo, self).__init__()
49+
50+
self.RecurType = "Automated"
51+
self.Name = ""
52+
self.Active = False
53+
54+
55+
class Recurring():
56+
class_dict = {
57+
"RecurringInfo": RecurringInfo,
58+
"RecurDataRef": Ref
59+
}
60+
61+
62+
class RecurringBill(Bill):
63+
class_dict = {**Bill.class_dict, **Recurring.class_dict}
64+
65+
66+
class RecurringPurchase(Purchase):
67+
class_dict = {**Purchase.class_dict, **Recurring.class_dict}
68+
69+
70+
class RecurringCreditMemo(CreditMemo):
71+
class_dict = {**CreditMemo.class_dict, **Recurring.class_dict}
72+
73+
74+
class RecurringDeposit(Deposit):
75+
class_dict = {**Deposit.class_dict, **Recurring.class_dict}
76+
77+
78+
class RecurringEstimate(Estimate):
79+
class_dict = {**Estimate.class_dict, **Recurring.class_dict}
80+
81+
82+
class RecurringInvoice(Invoice):
83+
class_dict = {**Invoice.class_dict, **Recurring.class_dict}
84+
85+
86+
class RecurringJournalEntry(JournalEntry):
87+
class_dict = {**JournalEntry.class_dict, **Recurring.class_dict}
88+
89+
90+
class RecurringRefundReceipt(RefundReceipt):
91+
class_dict = {**RefundReceipt.class_dict, **Recurring.class_dict}
92+
93+
94+
class RecurringSalesReceipt(SalesReceipt):
95+
class_dict = {**SalesReceipt.class_dict, **Recurring.class_dict}
96+
97+
98+
class RecurringTransfer(Transfer):
99+
class_dict = {**Transfer.class_dict, **Recurring.class_dict}
100+
101+
102+
class RecurringVendorCredit(VendorCredit):
103+
class_dict = {**VendorCredit.class_dict, **Recurring.class_dict}
104+
105+
106+
class RecurringPurchaseOrder(PurchaseOrder):
107+
class_dict = {**PurchaseOrder.class_dict, **Recurring.class_dict}
108+
109+
110+
@python_2_unicode_compatible
111+
class RecurringTransaction(QuickbooksBaseObject, ReadMixin, UpdateNoIdMixin, ListMixin, DeleteNoIdMixin):
112+
"""
113+
QBO definition: A RecurringTransaction object refers to scheduling creation of transactions,
114+
set up reminders and create transaction template for later use.
115+
This feature is available in QuickBooks Essentials and Plus SKU.
116+
"""
117+
class_dict = {
118+
"Bill": RecurringBill,
119+
"Purchase": RecurringPurchase,
120+
"CreditMemo": RecurringCreditMemo,
121+
"Deposit": RecurringDeposit,
122+
"Estimate": RecurringEstimate,
123+
"Invoice": RecurringInvoice,
124+
"JournalEntry": RecurringJournalEntry,
125+
"RefundReceipt": RecurringRefundReceipt,
126+
"SalesReceipt": RecurringSalesReceipt,
127+
"Transfer": RecurringTransfer,
128+
"VendorCredit": RecurringVendorCredit,
129+
"PurchaseOrder": RecurringPurchaseOrder
130+
}
131+
132+
qbo_object_name = "RecurringTransaction"

setup.cfg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,11 @@ max-complexity = 10
77
filename = *.py
88
format = default
99
exclude =/quickbooks/objects/__init__.py
10+
11+
[coverage:run]
12+
branch = True
13+
omit = src/db/env.py,src/db/versions/* # define paths to omit
14+
15+
[coverage:report]
16+
show_missing = True
17+
skip_covered = True

tests/integration/test_account.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ def test_create(self):
2020
self.id = account.Id
2121
query_account = Account.get(account.Id, qb=self.qb_client)
2222

23-
self.assertEquals(account.Id, query_account.Id)
24-
self.assertEquals(query_account.Name, self.name)
25-
self.assertEquals(query_account.AcctNum, self.account_number)
23+
self.assertEqual(account.Id, query_account.Id)
24+
self.assertEqual(query_account.Name, self.name)
25+
self.assertEqual(query_account.AcctNum, self.account_number)
2626

2727
def test_update(self):
2828
account = Account.filter(Name=self.name, qb=self.qb_client)[0]
@@ -31,4 +31,4 @@ def test_update(self):
3131
account.save(qb=self.qb_client)
3232

3333
query_account = Account.get(account.Id, qb=self.qb_client)
34-
self.assertEquals(query_account.Name, "Updated Name {0}".format(self.account_number))
34+
self.assertEqual(query_account.Name, "Updated Name {0}".format(self.account_number))

0 commit comments

Comments
 (0)