Wednesday, October 7, 2015

Create PDF invoices with Python and xtopdf

By Vasudev Ram




This post shows the basics of how to create a PDF invoice using Python and xtopdf, my Python library for PDF generation.

Here is the code, in file InvoiceToPDF.py. It's rudimentary, but shows the basics involved, and can be built upon to create more complex invoices with more information:
'''
InvoiceToPDF.py
This program shows how to convert invoice data from
Python data structures into a PDF invoice.
Author: Vasudev Ram
Copyright 2015 Vasudev Ram
'''

import sys
import time
from PDFWriter import PDFWriter

def error_exit(message):
    sys.stderr.write(message)
    sys.exit(1)

def InvoiceToPDF(pdf_filename, data):
    try:
        # Get the invoice data from the dict.
        font_name = data['font_name']
        font_size = data['font_size']
        header = data['header']
        footer = data['footer']
        invoice_number = data['invoice_number']
        invoice_customer = data['invoice_customer']
        invoice_date_time = data['invoice_date_time']
        invoice_line_items = data['invoice_line_items']
    except KeyError as ke:
        error_exit("KeyError: {}".format(ke))
    try:
        with PDFWriter(pdf_filename) as pw:
            # Generate the PDF invoice from the data.
            pw.setFont(font_name, font_size)
            pw.setHeader(header)
            pw.setFooter(footer)
            pw.writeLine('-' * 60)
            pw.writeLine('Invoice Number: ' + str(invoice_number))
            pw.writeLine('Invoice Customer: ' + invoice_customer)
            pw.writeLine('Invoice Date & Time: ' + invoice_date_time)
            pw.writeLine('-' * 60)
            pw.writeLine('Invoice line items:')
            pw.writeLine('S. No.'.zfill(5) + ' ' + 'Description'.ljust(10) + \
            ' ' + 'Unit price'.ljust(10) + ' ' + 'Quantity'.ljust(10) + ' ' + \
            str('Ext. Price').rjust(8))
            pw.writeLine('-' * 60)
            sum_ext_price = 0
            for line_item in invoice_line_items:
                id, desc, price, quantity = line_item
                pw.writeLine(str(id).zfill(5) + ' ' + desc.ljust(10) + \
                ' ' + str(price).rjust(10) + ' ' + str(quantity).rjust(10) + \
                str(price * quantity).rjust(10))
                sum_ext_price += price * quantity
            pw.writeLine('-' * 60)
            pw.writeLine('Total:'.rjust(38) + str(sum_ext_price).rjust(10))
            pw.writeLine('-' * 60)
    except IOError as ioe:
        error_exit("IOError: {}".format(ioe))
    except Exception as e:
        error_exit("Exception: {}".format(e))

def testInvoiceToPDF(pdf_filename):
    # Get the Unix-style date from the system ...
    cdt = time.ctime(time.time()).split()
    # ... and format it a little differently.
    current_date_time = cdt[0] + ' ' + cdt[1] + ' ' + cdt[2] + \
    ', ' + cdt[4] + ', ' + cdt[3][:5]
    data = { \
        'font_name': 'Courier', \
        'font_size': 12, \
        'header': 'Customer Invoice', \
        'footer': 'Generated by xtopdf: http://bit.ly/xtopdf', \
        'invoice_number': 12345, \
        'invoice_customer': 'Mr. Vasudev Ram', \
        'invoice_date_time': current_date_time, \
        'invoice_line_items': \
        [
            [ 01, 'Chair', 100, 10 ], \
            [ 02, 'Table', 200, 20 ], \
            [ 03, 'Cupboard', 300, 30 ], \
            [ 04, 'Bed', 400, 40 ], \
            [ 05, 'Wardrobe', 500, 50 ], \
        ]
    }
    InvoiceToPDF(pdf_filename, data)
    
def main():
    if len(sys.argv) != 2:
        error_exit("Usage: {} pdf_filename".format(sys.argv[0]))
    testInvoiceToPDF(sys.argv[1]) 

if __name__ == '__main__':
    main()
Run the program with a command like:
$ python InvoiceToPDF.py Invoice.pdf
Here is a screenshot of the resulting PDF invoice in Foxit Reader:
Note: The use of the dict named data is not strictly needed. I simply used it to illustrate the technique of packing the multiple data items needed for the invoice, into a single dict, and then unpack those needed items in the function that actually generates the PDF. I could have done away with the dict and just used standalone variables in this simple program. But in a larger program where the invoice data is collected / generated in one or more functions, and the PDF is generated in another function, this technique or something similar can be of use, to reduce the number of individual arguments that need to be passed around.

- Enjoy.

- Vasudev Ram - Online Python training and programming

Dancing Bison Enterprises

Signup to hear about new products and services that I create.

Posts about Python  Posts about xtopdf

No comments: