#! /usr/bin/env python # -*- coding: utf-8 -*- # # PyKota : Print Quotas for CUPS # # (c) 2003-2013 Jerome Alet # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # $Id$ # # """An invoice generator for PyKota""" import sys import os import pwd import time import cStringIO try : from reportlab.pdfgen import canvas from reportlab.lib import pagesizes from reportlab.lib.units import cm except ImportError : hasRL = False else : hasRL = True try : import PIL.Image except ImportError : hasPIL = False else : hasPIL = True import pykota.appinit from pykota.utils import run from pykota.commandline import PyKotaOptionParser, \ checkandset_pagesize, \ checkandset_positiveint, \ checkandset_percent from pykota.pdfutils import getPageSize from pykota.errors import PyKotaToolError, PyKotaCommandLineError from pykota.tool import PyKotaTool from pykota.progressbar import Percent class PKInvoice(PyKotaTool) : """A class for invoice generator.""" validfilterkeys = [ "username", "printername", "hostname", "jobid", "billingcode", "start", "end", ] def printVar(self, label, value, size) : """Outputs a variable onto the PDF canvas. Returns the number of points to substract to current Y coordinate. """ xcenter = (self.pagesize[0] / 2.0) - 1*cm self.canvas.saveState() self.canvas.setFont("Helvetica-Bold", size) self.canvas.setFillColorRGB(0, 0, 0) self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label) self.canvas.setFont("Courier-Bold", size) self.canvas.setFillColorRGB(0, 0, 1) self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value) self.canvas.restoreState() self.ypos -= (size + 4) def pagePDF(self, invoicenumber, name, values, unit, vat) : """Generates a new page in the PDF document.""" amount = values["nbcredits"] if amount : # is there's something due ? ht = ((amount * 10000.0) / (100.0 + vat)) / 100.0 vatamount = amount - ht self.canvas.doForm("background") self.ypos = self.yorigine - (cm + 20) self.printVar(_("Invoice"), "#%s" % invoicenumber, 22) self.printVar(_("Username"), name, 22) self.ypos -= 20 datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace") self.printVar(_("Edited on"), datetime, 14) self.ypos -= 20 self.printVar(_("Number of jobs printed"), str(values["nbjobs"]), 18) self.printVar(_("Number of pages printed"), str(values["nbpages"]), 18) self.ypos -= 20 self.printVar(_("Amount due"), "%.3f %s" % (amount, unit), 18) if vat : self.ypos += 8 self.printVar("%s (%.2f%%)" % (_("Included VAT"), vat), "%.3f %s" % (vatamount, unit), 14) self.canvas.showPage() return 1 return 0 def initPDF(self, logo) : """Initializes the PDF document.""" self.pdfDocument = cStringIO.StringIO() self.canvas = c = canvas.Canvas(self.pdfDocument, \ pagesize=self.pagesize, \ pageCompression=1) c.setAuthor(self.effectiveUserName) c.setTitle(_("PyKota invoices")) c.setSubject(_("Invoices generated with PyKota")) self.canvas.beginForm("background") self.canvas.saveState() self.ypos = self.pagesize[1] - (2 * cm) xcenter = self.pagesize[0] / 2.0 if logo : try : imglogo = PIL.Image.open(logo) except IOError : self.printInfo("Unable to open image %s" % logo, "warn") else : (width, height) = imglogo.size multi = float(width) / (8 * cm) width = float(width) / multi height = float(height) / multi self.ypos -= height c.drawImage(logo, xcenter - (width / 2.0), \ self.ypos, \ width, height) self.ypos -= (cm + 20) self.canvas.setFont("Helvetica-Bold", 14) self.canvas.setFillColorRGB(0, 0, 0) msg = _("Here's the invoice for your printouts") self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg) self.yorigine = self.ypos self.canvas.restoreState() self.canvas.endForm() def endPDF(self, fname) : """Flushes the PDF generator.""" self.canvas.save() if fname != "-" : outfile = open(fname, "w") outfile.write(self.pdfDocument.getvalue()) outfile.close() else : sys.stdout.write(self.pdfDocument.getvalue()) sys.stdout.flush() def genInvoices(self, peruser, logo, outfname, firstnumber, unit, vat) : """Generates the invoices file.""" if len(peruser) : percent = Percent(self, size=len(peruser)) if outfname != "-" : percent.display("%s...\n" % _("Generating invoices")) self.initPDF(logo) number = firstnumber for (name, values) in peruser.items() : number += self.pagePDF(number, name, values, unit, vat) if outfname != "-" : percent.oneMore() if number > firstnumber : self.endPDF(outfname) if outfname != "-" : percent.done() def main(self, arguments, options) : """Generate invoices.""" if not hasRL : raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org" if not hasPIL : raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads" self.adminOnly() self.pagesize = getPageSize(options.pagesize) extractonly = {} for filterexp in arguments : if filterexp.strip() : try : (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")] filterkey = filterkey.lower() if filterkey not in self.validfilterkeys : raise ValueError except ValueError : raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp else : extractonly.update({ filterkey : filtervalue }) percent = Percent(self) outfname = options.output.strip().encode(sys.getfilesystemencoding()) if outfname != "-" : percent.display("%s..." % _("Extracting datas")) username = extractonly.get("username") if username : user = self.storage.getUser(username) else : user = None printername = extractonly.get("printername") if printername : printer = self.storage.getPrinter(printername) else : printer = None start = extractonly.get("start") end = extractonly.get("end") (start, end) = self.storage.cleanDates(start, end) jobs = self.storage.retrieveHistory(user=user, printer=printer, hostname=extractonly.get("hostname"), billingcode=extractonly.get("billingcode"), jobid=extractonly.get("jobid"), start=start, end=end, limit=0) peruser = {} nbjobs = 0 nbpages = 0 nbcredits = 0.0 percent.setSize(len(jobs)) if outfname != "-" : percent.display("\n") for job in jobs : if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) : nbpages += job.JobSize nbcredits += job.JobPrice counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 }) counters["nbpages"] += job.JobSize counters["nbcredits"] += job.JobPrice counters["nbjobs"] += 1 nbjobs += 1 if outfname != "-" : percent.oneMore() if outfname != "-" : percent.done() self.genInvoices(peruser, options.logo.strip().encode(sys.getfilesystemencoding()), outfname, options.number, options.unit or _("Credits"), options.vat) if outfname != "-" : nbusers = len(peruser) self.display("%s\n" % (_("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \ % locals())) if __name__ == "__main__" : parser = PyKotaOptionParser(description=_("Invoice generator for PyKota."), usage="pkinvoice [options] [filterexpr]") parser.add_option("-l", "--logo", dest="logo", default=u"/usr/share/pykota/logos/pykota.jpeg", help=_("The image to use as a logo. The logo will be drawn at the center top of the page. The default logo is %default.")) parser.add_option("-p", "--pagesize", type="string", action="callback", callback=checkandset_pagesize, dest="pagesize", default=u"A4", help=_("Set the size of the page. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default page size is %default.")) parser.add_option("-n", "--number", dest="number", type="int", action="callback", callback=checkandset_positiveint, default=1, help=_("Set the number of the first invoice. This number will automatically be incremented for each invoice. The default value is %default.")) parser.add_option("-o", "--output", dest="output", type="string", default=u"-", help=_("The name of the file to which the PDF invoices will be written. If not set or set to '%default', the PDF document will be sent to the standard output.")) # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861 # TODO : we can't use 'default=_("Credits")' for this option parser.add_option("-u", "--unit", dest="unit", type="string", help=_("The name of the unit to use on the invoices. The default value is 'Credits' or its locale translation.")) parser.add_option("-V", "--vat", dest="vat", type="float", action="callback", callback=checkandset_percent, default=0.0, help=_("The value in percent of the applicable VAT to be exposed. The default is %default, meaning no VAT.")) parser.add_filterexpression("username", _("User's name")) parser.add_filterexpression("printername", _("Printer's name")) parser.add_filterexpression("hostname", _("Host's name")) parser.add_filterexpression("jobid", _("Job's id")) parser.add_filterexpression("billingcode", _("Job's billing code")) parser.add_filterexpression("start", _("Job's date of printing")) parser.add_filterexpression("end", _("Job's date of printing")) parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30', _("This would generate a PDF document containing invoices for all users who have spent some credits last month. Amounts would be in EURO and not VAT information would be included.")) run(parser, PKInvoice)