Skip to content
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

0.9.5 #328

Merged
merged 20 commits into from
Nov 1, 2023
Merged

0.9.5 #328

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Changelog
=========
* 0.9.5 (November 1, 2023)
* Added the ability to void all voidable QB types
* Added to_ref to CreditMemo object
* Added ProjectRef and ShipFromAddr to Estimate
* Added missing initialization for objects on DiscountLineDetail, Estimate, Employee, and Invoice

* 0.9.4 (August 29, 2023)
* Removed python 2 compatible decorators
* Removed python 2 dependencies
Expand Down
3 changes: 0 additions & 3 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,3 @@ simplejson = ">=3.19.1"
nose = "*"
coverage = "*"
twine = "*"

[requires]
python_version = "3.8"
533 changes: 282 additions & 251 deletions Pipfile.lock

Large diffs are not rendered by default.

57 changes: 51 additions & 6 deletions quickbooks/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,66 @@ def send(self, qb=None, send_to=None):


class VoidMixin(object):

def get_void_params(self):
qb_object_params_map = {
"Payment": {
"operation": "update",
"include": "void"
},
"SalesReceipt": {
"operation": "update",
"include": "void"
},
"BillPayment": {
"operation": "update",
"include": "void"
},
"Invoice": {
"operation": "void",
},
}
# setting the default operation to void (the original behavior)
return qb_object_params_map.get(self.qbo_object_name, {"operation": "void"})

def get_void_data(self):
qb_object_params_map = {
"Payment": {
"Id": self.Id,
"SyncToken": self.SyncToken,
"sparse": True
},
"SalesReceipt": {
"Id": self.Id,
"SyncToken": self.SyncToken,
"sparse": True
},
"BillPayment": {
"Id": self.Id,
"SyncToken": self.SyncToken,
"sparse": True
},
"Invoice": {
"Id": self.Id,
"SyncToken": self.SyncToken,
},
}
# setting the default operation to void (the original behavior)
return qb_object_params_map.get(self.qbo_object_name, {"operation": "void"})

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

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

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

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': 'void'})

data = self.get_void_data()
params = self.get_void_params()
results = qb.post(url, json.dumps(data), params=params)

return results

Expand Down
2 changes: 1 addition & 1 deletion quickbooks/objects/attachable.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def save(self, qb=None):
else:
json_data = qb.create_object(self.qbo_object_name, self.to_json(), _file_path=self._FilePath)

if self.FileName:
if self.Id is None and self.FileName:
obj = type(self).from_json(json_data['AttachableResponse'][0]['Attachable'])
else:
obj = type(self).from_json(json_data['Attachable'])
Expand Down
4 changes: 2 additions & 2 deletions quickbooks/objects/billpayment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .base import QuickbooksBaseObject, Ref, LinkedTxn, QuickbooksManagedObject, LinkedTxnMixin, \
QuickbooksTransactionEntity
from ..mixins import DeleteMixin
from ..mixins import DeleteMixin, VoidMixin


class CheckPayment(QuickbooksBaseObject):
Expand Down Expand Up @@ -47,7 +47,7 @@ def __str__(self):
return str(self.Amount)


class BillPayment(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity, LinkedTxnMixin):
class BillPayment(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity, LinkedTxnMixin, VoidMixin):
"""
QBO definition: A BillPayment entity represents the financial transaction of payment
of bills that the business owner receives from a vendor for goods or services purchased
Expand Down
8 changes: 8 additions & 0 deletions quickbooks/objects/creditmemo.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,11 @@ def __init__(self):

def __str__(self):
return str(self.TotalAmt)

def to_ref(self):
ref = Ref()

ref.type = self.qbo_object_name
ref.value = self.Id

return ref
1 change: 1 addition & 0 deletions quickbooks/objects/detailline.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __init__(self):
self.Discount = None
self.ClassRef = None
self.TaxCodeRef = None
self.DiscountAccountRef = None
self.PercentBased = False
self.DiscountPercent = 0

Expand Down
1 change: 1 addition & 0 deletions quickbooks/objects/employee.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self):
self.BillableTime = False

self.PrimaryAddr = None
self.PrimaryPhone = None

def __str__(self):
return self.DisplayName
Expand Down
5 changes: 5 additions & 0 deletions quickbooks/objects/estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ class Estimate(DeleteMixin,
class_dict = {
"BillAddr": Address,
"ShipAddr": Address,
"ShipFromAddr": Address,
"CustomerRef": Ref,
"ProjectRef": Ref,
"TxnTaxDetail": TxnTaxDetail,
"CustomerMemo": CustomerMemo,
"BillEmail": EmailAddress,
Expand Down Expand Up @@ -64,9 +66,12 @@ def __init__(self):
self.AcceptedDate = None
self.GlobalTaxCalculation = "TaxExcluded"
self.BillAddr = None
self.DepartmentRef = None
self.ShipAddr = None
self.ShipFromAddr = None
self.BillEmail = None
self.CustomerRef = None
self.ProjectRef = None
self.TxnTaxDetail = None
self.CustomerMemo = None
self.ClassRef = None
Expand Down
2 changes: 2 additions & 0 deletions quickbooks/objects/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def __init__(self):
self.TxnTaxDetail = None
self.DeliveryInfo = None
self.RecurDataRef = None
self.SalesTermRef = None
self.ShipMethodRef = None
self.TaxExemptionRef = None
self.MetaData = None

Expand Down
23 changes: 2 additions & 21 deletions quickbooks/objects/payment.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
LinkedTxnMixin, MetaData
from ..client import QuickBooks
from .creditcardpayment import CreditCardPayment
from ..mixins import DeleteMixin
from ..mixins import DeleteMixin, VoidMixin
import json


Expand All @@ -21,7 +21,7 @@ def __str__(self):
return str(self.Amount)


class Payment(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity, LinkedTxnMixin):
class Payment(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity, LinkedTxnMixin, VoidMixin):
"""
QBO definition: A Payment entity records a payment in QuickBooks. The payment can be
applied for a particular customer against multiple Invoices and Credit Memos. It can also
Expand Down Expand Up @@ -81,24 +81,5 @@ 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)
4 changes: 2 additions & 2 deletions quickbooks/objects/salesreceipt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
EmailAddress, QuickbooksTransactionEntity, LinkedTxn
from .tax import TxnTaxDetail
from .detailline import DetailLine
from ..mixins import QuickbooksPdfDownloadable, DeleteMixin
from ..mixins import QuickbooksPdfDownloadable, DeleteMixin, VoidMixin


class SalesReceipt(DeleteMixin, QuickbooksPdfDownloadable, QuickbooksManagedObject,
QuickbooksTransactionEntity, LinkedTxnMixin):
QuickbooksTransactionEntity, LinkedTxnMixin, VoidMixin):
"""
QBO definition: SalesReceipt represents the sales receipt that is given to a customer.
A sales receipt is similar to an invoice. However, for a sales receipt, payment is received
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def read(*parts):
return fp.read()


VERSION = (0, 9, 4)
VERSION = (0, 9, 5)
version = '.'.join(map(str, VERSION))

setup(
Expand Down
50 changes: 43 additions & 7 deletions tests/integration/test_billpayment.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from datetime import datetime

from quickbooks.objects import AccountBasedExpenseLine, Ref, AccountBasedExpenseLineDetail
from quickbooks.objects.account import Account
from quickbooks.objects.bill import Bill
from quickbooks.objects.billpayment import BillPayment, BillPaymentLine, CheckPayment
Expand All @@ -14,12 +15,30 @@ def setUp(self):
self.account_number = datetime.now().strftime('%d%H%M')
self.name = "Test Account {0}".format(self.account_number)

def test_create(self):
def create_bill(self, amount):
bill = Bill()
line = AccountBasedExpenseLine()
line.Amount = amount
line.DetailType = "AccountBasedExpenseLineDetail"

account_ref = Ref()
account_ref.type = "Account"
account_ref.value = 1
line.AccountBasedExpenseLineDetail = AccountBasedExpenseLineDetail()
line.AccountBasedExpenseLineDetail.AccountRef = account_ref
bill.Line.append(line)

vendor = Vendor.all(max_results=1, qb=self.qb_client)[0]
bill.VendorRef = vendor.to_ref()

return bill.save(qb=self.qb_client)

def create_bill_payment(self, bill, amount, private_note, pay_type):
bill_payment = BillPayment()

bill_payment.PayType = "Check"
bill_payment.TotalAmt = 200
bill_payment.PrivateNote = "Private Note"
bill_payment.PayType = pay_type
bill_payment.TotalAmt = amount
bill_payment.PrivateNote = private_note

vendor = Vendor.all(max_results=1, qb=self.qb_client)[0]
bill_payment.VendorRef = vendor.to_ref()
Expand All @@ -31,14 +50,18 @@ def test_create(self):
ap_account = Account.where("AccountSubType = 'AccountsPayable'", qb=self.qb_client)[0]
bill_payment.APAccountRef = ap_account.to_ref()

bill = Bill.all(max_results=1, qb=self.qb_client)[0]

line = BillPaymentLine()
line.LinkedTxn.append(bill.to_linked_txn())
line.Amount = 200

bill_payment.Line.append(line)
bill_payment.save(qb=self.qb_client)
return bill_payment.save(qb=self.qb_client)

def test_create(self):
# create new bill for testing, reusing the same bill will cause Line to be empty
# and the new bill payment will be voided automatically
bill = self.create_bill(amount=200)
bill_payment = self.create_bill_payment(bill, 200, "Private Note", "Check")

query_bill_payment = BillPayment.get(bill_payment.Id, qb=self.qb_client)

Expand All @@ -48,3 +71,16 @@ def test_create(self):

self.assertEqual(len(query_bill_payment.Line), 1)
self.assertEqual(query_bill_payment.Line[0].Amount, 200.0)

def test_void(self):
bill = self.create_bill(amount=200)
bill_payment = self.create_bill_payment(bill, 200, "Private Note", "Check")
query_payment = BillPayment.get(bill_payment.Id, qb=self.qb_client)
self.assertEqual(query_payment.TotalAmt, 200.0)
self.assertNotIn('Voided', query_payment.PrivateNote)

bill_payment.void(qb=self.qb_client)
query_payment = BillPayment.get(bill_payment.Id, qb=self.qb_client)

self.assertEqual(query_payment.TotalAmt, 0.0)
self.assertIn('Voided', query_payment.PrivateNote)
11 changes: 11 additions & 0 deletions tests/integration/test_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,14 @@ def test_delete(self):

query_invoice = Invoice.filter(Id=invoice_id, qb=self.qb_client)
self.assertEqual([], query_invoice)

def test_void(self):
customer = Customer.all(max_results=1, qb=self.qb_client)[0]
invoice = self.create_invoice(customer)
invoice_id = invoice.Id
invoice.void(qb=self.qb_client)

query_invoice = Invoice.get(invoice_id, qb=self.qb_client)
self.assertEqual(query_invoice.Balance, 0.0)
self.assertEqual(query_invoice.TotalAmt, 0.0)
self.assertIn('Voided', query_invoice.PrivateNote)
Loading