ej2 / python-quickbooks

A Python library for accessing the Quickbooks API.
MIT License
402 stars 194 forks source link

Creating an Invoice #103

Closed johnnydisco closed 6 years ago

johnnydisco commented 6 years ago

I’ve successfully linked and created a QBO customer but I can’t work out what I need to do to create an invoice.

Can anyone explain invoice = Invoice() please and what fields I need to create it? I can’t work out the Line or how to select a customer for it.

mark-mishyn commented 6 years ago

Hello @johnnydisco There is simple example how to create single-line Invoice with only required fields.

from quickbooks.objects import (Invoice, 
                                SalesItemLineDetail, 
                                SalesItemLine)

customer_ref = Ref()
customer_ref.value = 123
customer_ref.name = 'Ivan Petrov'
customer_ref.type = 'Customer'
# or customer_ref = Customer.get(123, qb=client).to_ref()

line_detail = SalesItemLineDetail()
line_detail.UnitPrice = 100  # in dollars
line_detail.Qty = 1  # quantity can be decimal

line = SalesItemLine()
line.Amount = 100  # in dollars
line.SalesItemLineDetail = line_detail

invoice = Invoice()
invoice.CustomerRef = customer_ref
invoice.Line = [line]

client = QuickBooks(...)

invoice.save(qb=client)
lmorroni commented 6 years ago

I was able to follow this and get an invoice created but then I try to send the invoice via email using the following:

            invoice.save(qb=qb_client)
            invoice.send_invoice("john@doe.com")

I get this error

AttributeError: 'Invoice' object has no attribute 'api_url'

Looking in invoice.py

def send_invoice(self, send_to=None):
        url = self.api_url + "/company/{0}/invoice/{1}/send".format(self.company_id, self.Id)
        results = self.make_request("POST", url)

        return results

It seems like it's looking for client properties in the invoice object. What am I missing?

lmorroni commented 6 years ago

I took a look at the tests and see they use invoice.send() instead of invoice.send_invoice() I tried this like so:

            invoice = Invoice()
            invoice.Id = 291
            invoice.send(qb=qb_client)

...and I get the following error:

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/web/app.py", line 259, in bt_sub_hook
    invoice.send(qb=qb_client)
  File "/web/3rdparty/quickbooks/quickbooks/mixins.py", line 108, in send
    results = qb.misc_operation(end_point)
TypeError: misc_operation() missing 1 required positional argument: 'request_body'
lmorroni commented 6 years ago

Anyone following this thread? This is a show stopper.

ej2 commented 6 years ago

I think I see the problem. I will work on a fix and release an update later this week.

lmorroni commented 6 years ago

Not sure if this helps but I pulled commit 3611022 and tried again. Here's the call and the corresponding error

invoice.send("trey@phish.com",qb=qb_client)
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1997, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
    response = self.handle_exception(e)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
    raise value
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/web/app.py", line 289, in bt_sub_hook
    invoice.send("larry@morroni.com",qb=qb_client)
  File "/web/3rdparty/quickbooks/quickbooks/mixins.py", line 112, in send
    results = qb.misc_operation(end_point, content_type="application/octet-stream")
  File "/web/3rdparty/quickbooks/quickbooks/client.py", line 298, in misc_operation
    results = self.make_request("GET", url, request_body, content_type)
  File "/web/3rdparty/quickbooks/quickbooks/client.py", line 204, in make_request
    raise AuthorizationException("Application authentication failed", detail=req.text)
quickbooks.exceptions.AuthorizationException: QB Auth Exception: Application authentication failed
ej2 commented 6 years ago

Can you check and make sure that your qb_client is set up properly. "Application authentication failed" typically means that you don't have something set up correctly.

lmorroni commented 6 years ago

Yes I agree that's what it sounds like but the invoice.save() works without issue as do a number of other operations that require authentication.

lmorroni commented 6 years ago

Not sure this helps but looks like this is what we are getting back

{"warnings":null,"intuitObject":null,"fault":{"error":[{"message":"message=ApplicationAuthenticationFailed; errorCode=003200; statusCode=401","detail":"SignatureBaseString: GET&https%3A%2F%2Fsandbox-quickbooks.api.intuit.com%2Fv3%2Fcompany%2F123145844963419%2Finvoice%2F352%2Fsend&oauth_consumer_key%3DqyprdLiUxYMs4EBskkejFji8cHbIic%26oauth_nonce%3D9ddc85e91e848d030b8ba0ed76bdd57b646c60bf%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1522344247%26oauth_token%3Dqyprdj79RtfIkBSkCgJZKdViu9hYCJlY2DH9vhNuGH292KNV%26oauth_version%3D1.0%26sendTo%3Dtrey%2540phish.com","code":"3200","element":null}],"type":"AUTHENTICATION"},"report":null,"queryResponse":null,"batchItemResponse":[],"attachableResponse":[],"syncErrorResponse":null,"requestId":null,"time":1522344133155,"status":null,"cdcresponse":[]}
lmorroni commented 6 years ago

I pulled from master today and it's now using a POST instead of GET. Here's how I call it:

invoice.send(qb=qb_client,send_to="trey@phish.com")

And resulting error

{"warnings":null,"intuitObject":null,"fault":{"error":[{"message":"message=ApplicationAuthenticationFailed; errorCode=003200; statusCode=401","detail":"SignatureBaseString: POST&https%3A%2F%2Fsandbox-quickbooks.api.intuit.com%2Fv3%2Fcompany%2F123145844963419%2FInvoice%2F357%2Fsend&oauth_consumer_key%3DqyprdLiUxYMs4EBskkejFji8cHbIic%26oauth_nonce%3D58c2db036d2849105135b1cf4f745b90f915ff99%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1522345028%26oauth_token%3Dqyprdj79RtfIkBSkCgJZKdViu9hYCJlY2DH9vhNuGH292KNV%26oauth_version%3D1.0%26sendTo%3Dtrey%2540phish.com","code":"3200","element":null}],"type":"AUTHENTICATION"},"report":null,"queryResponse":null,"batchItemResponse":[],"attachableResponse":[],"syncErrorResponse":null,"requestId":null,"time":1522344912931,"status":null,"cdcresponse":[]}
lmorroni commented 6 years ago

Has anyone here had success sending an invoice through the QB Rest API? I tried using Postman and it also failed. I've posted about this on Intuit's developer forum here: https://help.developer.intuit.com/s/question/0D50f00005OLgujCAD/unsupported-operation-error-while-attempting-to-send-invoice-through-quickbooks-rest-api?t=1524511489114

Looking to discuss with others that may have also experienced this.
Thanks, Larry

lmorroni commented 6 years ago

For anyone following along at home, this turned out to be an issue with the content type. The content type for when you make a send email request needs to be application/octet-stream. I had to modify client.py and mixin.py. I'm not sure if this project is taking merge requests so I'm not making one unless I hear back.

client.py (added content_type parameter)

    def misc_operation(self, end_point, request_body, content_type='application/json'):
        url = self.api_url + "/company/{0}/{1}".format(self.company_id, end_point)
        results = self.make_request("POST", url, request_body, content_type)

        return results

mixins.py (set the object name to lowercase since the URL is case sensitive, also passed the octet-stream content type)

class SendMixin(object):
    def send(self, qb=None, send_to=None):
        if not qb:
            qb = QuickBooks()
        end_point = "{0}/{1}/send".format(self.qbo_object_name.lower(), self.Id)

        if send_to:
            end_point = "{0}?sendTo={1}".format(end_point, send_to)

        results = qb.misc_operation(end_point, None,'application/octet-stream')

        return results

Hopefully this helps someone else. Larry

ej2 commented 6 years ago

I have updated the client as you suggested.

ahmadberkeleypayment commented 3 years ago

invoice.send('aaaaaa@gmail.com'). 'str' object has no attribute 'misc_operation'. any solutions for this one??!

mark-mishyn commented 3 years ago

invoice.send('aaaaaa@gmail.com'). 'str' object has no attribute 'misc_operation'. any solutions for this one??!

try to use invoice.send(qb=qb_client, send_to="aaaaaa@gmail.com")