#encoding=utf-8 # Copyright (c) 2013 Billogram AB # # Permission is hereby granted, free of charge, to any person obtaining a copy of # this software and associated documentation files (the "Software"), to deal in # the Software without restriction, including without limitation the rights to # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of # the Software, and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. # This example code is written to work on Python 2.7 and 3.3. # Some things are a little uglier for this reason, please bear with us. from __future__ import unicode_literals, print_function, division # By default the billogram_api module exports just two symbols, the BillogramAPI # class and the BillogramExceptions namespace-class. # Effectively, importing * should be fine in all cases, although your coding # standards may prescribe otherwise. from billogram_api import * #from billogram_api import BillogramAPI, BillogramExceptions # same effect # Some of these examples need to work with dates from datetime import date, timedelta def create_connection(): """Create an API pseudo-connection object for the examples Either read configuration from a local configuration module, or from standard input if the module does not exist. """ try: from _testing_defaults import API_USER, API_KEY, API_URLBASE print("Using credentials from _testing_defaults") username = API_USER authkey = API_KEY api_urlbase = API_URLBASE or None except: # Python 2/3 compatibility try: input = raw_input except: pass # Get the credentials from standard input username = input("Enter API username: ").strip() authkey = input("Enter API authentication key: ").strip() api_urlbase = input("API URL base (or blank for default): ").strip() or None # The BillogramAPI constructor can optionally take api_base and user_agent if you # want or need to override those, but for regular production operation the defaults # should be correct. # For testing you may have been given an api_base URL for a testing environment. return BillogramAPI(username, authkey, api_base=api_urlbase) def example1(api): """Basic example, create, send and credit an invoice The invoice will be sent to customer number 1, and be for 1 unit of item 1. Will obviously fail if there are no customers or items with numbers 1 on the business account connected to. """ # Make a dictionary with the data for the billogram object we want to create # Skip non-mandatory fields in this example data = { # Specifying the recipient, must always be one from the database. 'customer': { # On creation, only the customer_no can be specified. 'customer_no': 1 }, # Specifying the items being invoiced for, can either be from the database or single-use ones 'items': [ { # This item specifies just an item_no, so it always uses one from the database. # If there is no item by this number, the call will fail since other mandatory # fields are then missing. # Note that item numbers are strings, they can contain non-numeric characters. 'item_no': '1', # You must always specify how many of each item is being invoiced for. 'count': 1 } ], # The inovoicing currency must always be given, although currently only SEK is supported. 'currency': 'SEK', # Have the due date be 35 days (5 weeks) in the future 'due_date': (date.today() + timedelta(days=35)).isoformat() } print("Creating and sending billogram with data:\n{}".format(prettyfy(data))) try: # Attempt to create and then send the billogram using the data. If this succeeds, # the result will be a billogram object, wrapping a dictionary with the state of # the billogram after the two operations. bg = api.billogram.create_and_send(data, 'Email') print("The result of creating the billogram object:\n{}".format(prettyfy(bg.data))) print("Now crediting the entire billogram (id {})".format(bg['id'])) # Credit the billogram, creates a new state in the billogram object and sends a # credit invoice to the recipient. The object is updated with the new state # after the operation. bg.credit_full() print("State of billogram after crediting:\n{}".format(prettyfy(bg.data))) except BillogramExceptions.BillogramAPIError as e: print("An API error occurred: {!r}".format(e)) def example2(api): """Find all "gadget" items and increase their price by 10% """ # First create a query object for the items class qry = api.items.query() # Set up some query parameters qry.filter_search('title', 'gadget') # items with "gadget" in somewhere their title # print some status print("Matched {} gadget items to change".format(qry.count)) # Loop over every page of results, processing all items for item in qry.iter_all(): print("Current price for item {} is {}".format(item['item_no'], item['price'])) # modify the item item.update({ 'price': round(item['price']*1.1, 2) }) print(" New price is {}".format(item['price'])) def example3(api): """Create or find a customer and invoice them """ customer_no = 12345 try: print("Trying to fetch customer {}".format(customer_no)) customer = api.customers.get(customer_no) print("Found the customer") except BillogramExceptions.ObjectNotFoundError: print("Customer not found, creating instead") customer_data = { "customer_no": customer_no, "name": "Terkel Testsson", "contact": { "name": "Terkel Testsson", "email": "terkel@example.com", }, "address": { "street_address": "Exempelgatan 123", "city": "Stockholm", "zipcode": "123 45", "country": "SE", }, "company_type": "individual", } customer = api.customers.create(customer_data) print("Customer was created") print("Trying to create billogram object") billogram_data = { "customer": { "customer_no": customer_no, }, "items": [ { "title": "Customer assistance", "description": "Phone conversation and physical warehouse search", "price": 300, "unit": "hour", "vat": 25, "count": 0.5, }, { "item_no": "20", "description": "Adding 0.14 extra for your convenience", "count": 3.14, }, ], "currency": "SEK", "invoice_fee": 50, "due_date": (date.today() + timedelta(days=30)).isoformat(), "automatic_reminders": False, } billogram = api.billogram.create(billogram_data) print("Billogram object created, id is {}".format(billogram['id'])) customer.update({"notes": "Last conversation was on invoice id {}".format(billogram['id']) }) print("Customer object updated before sending invoice") billogram.send('Email+Letter') print("Invoice has now been sent") def example4(api): """Find a fully paid or credited invoice and download all its PDF documents """ print("Querying for paid or credited billogram objects") query = api.billogram.query() query.filter_state_any('Paid', 'Credited') query.page_size = 1 bgs = query.get_page(1) if not bgs: print("No billogram found") return bg = bgs[0] print("Found billogram with id {0[id]}, state is {0[state]}".format(bg)) print("Getting full information for billogram object") bg.refresh() # the object is initially a compact one, refreshing it will get the full data print("Now processing events") for ev in bg['events']: print("{0[type]} event at {0[created_at]}".format(ev)) if ev['data'] and 'letter_id' in ev['data']: print(" - has letter_id {0[data][letter_id]}, getting it".format(ev)) try: pdf = bg.get_invoice_pdf(letter_id=ev['data']['letter_id']) print(" - pdf is {} bytes long".format(len(pdf))) except BillogramExceptions.ObjectNotAvailableYetError: print(" - pdf not created yet") except BillogramExceptions.ObjectNotFoundError: print(" - pdf was not found") def example5(api): """This example shows some error handling """ # A billogram dataset containing invalid items billogram_data = { "customer": { "customer_no": 12345 }, "items": [ # This item (0) is fine {'title': 'Test 1', 'price': 1, 'unit': 'unit', 'vat': 25, 'count': 1}, # This one (1) is fine too {'title': 'Test 2', 'price': -2, 'unit': 'kg', 'vat': 0, 'count': 1}, # Error here (2), title is empty {'title': '', 'price': 3, 'unit': 'kg', 'vat': 0, 'count': 1}, # Error here too (3), missing count {'title': 'Test 4', 'price': -10, 'vat': 0}, ], "currency": "SEK", "due_date": (date.today() + timedelta(days=30)).isoformat(), } # Attempt creating it try: print("Trying to create invalid billogram") bg = api.billogram.create(billogram_data) print("Billogram created?! This should not happen.") except BillogramExceptions.RequestDataError as e: print("Creating the billogram failed! Exception is {}".format(e.__name__)) if e.message: print("The error message returned is: {}".format(e.message)) if e.field: print("The context of the error is {} and the field name is '{}'".format(e.error_source, e.field)) if e.field_path: print("Additionally, the error is located in this sub-object: {}".format(e.field_path)) print("The expected error is 'Title not set', in the 'title' field of ['items', 2]") ######### From here on it's just housekeeping, no more examples ######### # Helper function to pretty-print the structures def prettyfy(o, level=''): nextlevel = level + ' ' if isinstance(o, dict): return '{\n' + ',\n'.join(['{}{!s}: {}'.format(nextlevel, k, prettyfy(v, nextlevel)) for k, v in o.items()]) + '\n' + level + '}' elif isinstance(o, list): return '[\n' + ',\n'.join(['{}{}'.format(nextlevel, prettyfy(v, nextlevel)) for v in iter(o)]) + '\n' + level + ']' elif isinstance(o, set): return '{\n' + ',\n'.join(['{}{}'.format(nextlevel, prettyfy(v, nextlevel)) for v in iter(o)]) + '\n' + level + '}' elif isinstance(o, tuple): return '(\n' + ',\n'.join(['{}{}'.format(nextlevel, prettyfy(v, nextlevel)) for v in iter(o)]) + '\n' + level + ')' else: return repr(o) # For running the examples from a terminal if __name__ == '__main__': print("Billogram v2 API examples") api = create_connection() print() print("Running example 1") example1(api) print() print("Running example 2") example2(api) print() print("Running example 3") example3(api) print() print("Running example 4") example4(api) print() print("Running example 5") example5(api) print() print("Finished running all examples")