163 lines
6.9 KiB
Python
163 lines
6.9 KiB
Python
|
"""
|
||
|
BSD license - created by Massimo Di Pierro
|
||
|
"""
|
||
|
from __future__ import print_function
|
||
|
from reportlab.pdfgen.canvas import Canvas
|
||
|
from reportlab.platypus import Table
|
||
|
from reportlab.lib.pagesizes import A4
|
||
|
from reportlab.lib.units import cm
|
||
|
from decimal import Decimal
|
||
|
import cStringIO
|
||
|
import datetime
|
||
|
|
||
|
def listify(item):
|
||
|
if isinstance(item,basestring):
|
||
|
item = item.split('\n')
|
||
|
return item
|
||
|
|
||
|
class PDF(object):
|
||
|
def __init__(self, page_size=A4, font_face='Helvetica'):
|
||
|
self.page_size = page_size
|
||
|
self.font_face = font_face
|
||
|
self.logo = None
|
||
|
def format_currency(self,value):
|
||
|
a = list(str(int(value)))
|
||
|
for k in range(len(a)-3,0,-3):
|
||
|
a.insert(k,',')
|
||
|
a = ''.join(a)
|
||
|
b = ("%.2f" % (value-int(value)))[2:]
|
||
|
return "%s.%s" % (a,b)
|
||
|
def draw(self, invoice, items_page=10):
|
||
|
""" Draws the invoice """
|
||
|
buffer = cStringIO.StringIO()
|
||
|
invoice_items = invoice['items']
|
||
|
pages = max((len(invoice_items)-2)/items_page+1,1)
|
||
|
canvas = Canvas(buffer, pagesize=self.page_size)
|
||
|
for page in range(pages):
|
||
|
canvas.translate(0, 29.7 * cm)
|
||
|
canvas.setFont(self.font_face, 10)
|
||
|
|
||
|
canvas.saveState()
|
||
|
canvas.setStrokeColorRGB(0.9, 0.5, 0.2)
|
||
|
canvas.setFillColorRGB(0.2, 0.2, 0.2)
|
||
|
canvas.setFont(self.font_face, 16)
|
||
|
canvas.drawString(1 * cm, -1 * cm, invoice.get('title',''))
|
||
|
if self.logo:
|
||
|
canvas.drawInlineImage(self.logo, 1 * cm, -1 * cm, 250, 16)
|
||
|
canvas.setLineWidth(4)
|
||
|
canvas.line(0, -1.25 * cm, 21.7 * cm, -1.25 * cm)
|
||
|
canvas.restoreState()
|
||
|
|
||
|
canvas.saveState()
|
||
|
notes = listify(invoice.get('notes',''))
|
||
|
textobject = canvas.beginText(1 * cm, -25 * cm)
|
||
|
for line in notes:
|
||
|
textobject.textLine(line)
|
||
|
canvas.drawText(textobject)
|
||
|
textobject = canvas.beginText(18 * cm, -28 * cm)
|
||
|
textobject.textLine('Pag.%s/%s' % (page+1,pages))
|
||
|
canvas.drawText(textobject)
|
||
|
canvas.restoreState()
|
||
|
|
||
|
canvas.saveState()
|
||
|
business_details = listify(invoice.get('from','FROM:'))
|
||
|
canvas.setFont(self.font_face, 9)
|
||
|
textobject = canvas.beginText(13 * cm, -2.5 * cm)
|
||
|
for line in business_details:
|
||
|
textobject.textLine(line)
|
||
|
canvas.drawText(textobject)
|
||
|
canvas.restoreState()
|
||
|
|
||
|
canvas.saveState()
|
||
|
client_info = listify(invoice.get('to','TO:'))
|
||
|
textobject = canvas.beginText(1.5 * cm, -2.5 * cm)
|
||
|
for line in client_info:
|
||
|
textobject.textLine(line)
|
||
|
canvas.drawText(textobject)
|
||
|
canvas.restoreState()
|
||
|
|
||
|
textobject = canvas.beginText(1.5 * cm, -6.75 * cm)
|
||
|
textobject.textLine(u'Invoice ID: %s' % invoice.get('id','<invoice id>'))
|
||
|
textobject.textLine(u'Invoice Date: %s' % invoice.get('date',datetime.date.today()))
|
||
|
textobject.textLine(u'Client: %s' % invoice.get('client_name','<invoice client>'))
|
||
|
canvas.drawText(textobject)
|
||
|
|
||
|
items = invoice_items[1:][page*items_page:(page+1)*items_page]
|
||
|
if items:
|
||
|
data = [invoice_items[0]]
|
||
|
for item in items:
|
||
|
data.append([
|
||
|
self.format_currency(x)
|
||
|
if isinstance(x,float) else x
|
||
|
for x in item])
|
||
|
righta = [k for k,v in enumerate(items[0])
|
||
|
if isinstance(v,(int,float,Decimal))]
|
||
|
if page == pages-1:
|
||
|
total = self.format_currency(invoice['total'])
|
||
|
else:
|
||
|
total = ''
|
||
|
data.append(['']*(len(items[0])-1)+[total])
|
||
|
colWidths = [2.5*cm]*len(items[0])
|
||
|
colWidths[1] = (21.5-2.5*len(items[0]))*cm
|
||
|
table = Table(data, colWidths=colWidths)
|
||
|
table.setStyle([
|
||
|
('FONT', (0, 0), (-1, -1), self.font_face),
|
||
|
('FONTSIZE', (0, 0), (-1, -1), 8),
|
||
|
('TEXTCOLOR', (0, 0), (-1, -1), (0.2, 0.2, 0.2)),
|
||
|
('GRID', (0, 0), (-1, -2), 1, (0.7, 0.7, 0.7)),
|
||
|
('GRID', (-1, -1), (-1, -1), 1, (0.7, 0.7, 0.7)),
|
||
|
('BACKGROUND', (0, 0), (-1, 0), (0.8, 0.8, 0.8)),
|
||
|
]+[('ALIGN',(k,0),(k,-1),'RIGHT') for k in righta])
|
||
|
tw, th, = table.wrapOn(canvas, 15 * cm, 19 * cm)
|
||
|
table.drawOn(canvas, 1 * cm, -8 * cm - th)
|
||
|
|
||
|
if page == pages-1:
|
||
|
items = invoice['totals'][1:]
|
||
|
if items:
|
||
|
data = [invoice['totals'][0]]
|
||
|
for item in items:
|
||
|
data.append([
|
||
|
self.format_currency(x)
|
||
|
if isinstance(x,float) else x
|
||
|
for x in item])
|
||
|
righta = [k for k,v in enumerate(items[0])
|
||
|
if isinstance(v,(int,float,Decimal))]
|
||
|
total = self.format_currency(invoice['total'])
|
||
|
data.append(['']*(len(items[0])-1)+[total])
|
||
|
colWidths = [2.5*cm]*len(items[0])
|
||
|
colWidths[1] = (21.5-2.5*len(items[0]))*cm
|
||
|
table = Table(data, colWidths=colWidths)
|
||
|
table.setStyle([
|
||
|
('FONT', (0, 0), (-1, -1), self.font_face),
|
||
|
('FONTSIZE', (0, 0), (-1, -1), 8),
|
||
|
('TEXTCOLOR', (0, 0), (-1, -1), (0.2, 0.2, 0.2)),
|
||
|
('GRID', (0, 0), (-1, -2), 1, (0.7, 0.7, 0.7)),
|
||
|
('GRID', (-1, -1), (-1, -1), 1, (0.7, 0.7, 0.7)),
|
||
|
('BACKGROUND', (0, 0), (-1, 0), (0.8, 0.8, 0.8)),
|
||
|
]+[('ALIGN',(k,0),(k,-1),'RIGHT') for k in righta])
|
||
|
tw, th, = table.wrapOn(canvas, 15 * cm, 19 * cm)
|
||
|
table.drawOn(canvas, 1 * cm, -18 * cm - th)
|
||
|
canvas.showPage()
|
||
|
canvas.save()
|
||
|
return buffer.getvalue()
|
||
|
|
||
|
if __name__=='__main__':
|
||
|
invoice = {
|
||
|
'title': 'Invoice - web2py.com',
|
||
|
'id': '00001',
|
||
|
'date': '10/10/2013',
|
||
|
'client_name': 'Nobody',
|
||
|
'from': 'FROM:\nweb2py.com\nWabash ave\nChicago',
|
||
|
'to': 'TO:\nNobody\nHis address',
|
||
|
'notes': 'no comment!',
|
||
|
'total': 650.00,
|
||
|
'items': [
|
||
|
['Codice','Desc','Quantity','Unit price','Total']]+[
|
||
|
['000001','Chair',2,10.0,20.0] for k in range(30)],
|
||
|
'totals': [
|
||
|
['Codice','Desc','Total']]+[
|
||
|
['000001','Chairs',600.0],
|
||
|
['','Tax',50.0]],
|
||
|
}
|
||
|
print(PDF().draw(invoice,items_page=20))
|