#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # PyKota Invoice generator # # PyKota - Print Quotas for CUPS and LPRng # # (c) 2003, 2004, 2005, 2006 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 2 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, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # $Id$ # # 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 = 0 else : hasRL = 1 try : import PIL.Image except ImportError : hasPIL = 0 else : hasPIL = 1 from pykota.tool import Percent, PyKotaToolError, PyKotaCommandLineError, crashed, N_ from pykota.dumper import DumPyKota __doc__ = N_("""pkinvoice v%(__version__)s (c) %(__years__)s %(__author__)s An invoice generator for PyKota. command line usage : pkinvoice [options] user1 user2 ... userN options : -v | --version Prints pkinvoice's version number then exits. -h | --help Prints this message then exits. -l | --logo img Use the image as the invoice's logo. The logo will be drawn at the center top of the page. The default logo is /usr/share/pykota/logos/pykota.jpeg -p | --pagesize sz Sets sz as the page size. Most well known page sizes are recognized, like 'A4' or 'Letter' to name a few. The default size is A4. -n | --number N Sets the number of the first invoice. This number will automatically be incremented for each invoice. -o | --output f.pdf Defines the name of the invoice file which will be generated as a PDF document. If not set or set to '-', the PDF document is sent to standard output. -u | --unit u Defines the name of the unit to use on the invoice. The default unit is 'Credits', optionally translated to your native language if it is supported by PyKota. -V | --vat p Sets the percent value of the applicable VAT to be exposed. The default is 0.0, meaning no VAT information will be included. -s | --start date Sets the starting date for the print jobs invoiced. -e | --end date Sets the ending date for the print jobs invoiced. user1 through userN can use wildcards if needed. If no user argument is used, a wildcard of '*' is assumed, meaning include all users. Dates formating with --start and --end : YYYY : year boundaries YYYYMM : month boundaries YYYYMMDD : day boundaries YYYYMMDDhh : hour boundaries YYYYMMDDhhmm : minute boundaries YYYYMMDDhhmmss : second boundaries yesterday[+-NbDays] : yesterday more or less N days (e.g. : yesterday-15) today[+-NbDays] : today more or less N days (e.g. : today-15) tomorrow[+-NbDays] : tomorrow more or less N days (e.g. : tomorrow-15) now[+-NbDays] : now more or less N days (e.g. now-15) 'now' and 'today' are not exactly the same since today represents the first or last second of the day depending on if it's used in a start= or end= date expression. The utility to be able to specify dates in the future is a question which remains to be answered :-) examples : $ pkinvoice --unit EURO --output invoices.pdf --start=now-30 Will generate a PDF document containing invoices for all users who have spent some credits last month. Invoices will be done in EURO. No VAT information will be included. """) class PKInvoice(DumPyKota) : """A class for pkinvoice.""" def getPageSize(self, pgsize) : """Returns the correct page size or None if not found.""" try : return getattr(pagesizes, pgsize.upper()) except AttributeError : try : return getattr(pagesizes, pgsize.lower()) except AttributeError : pass 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, entry, vat, start, end, unitname) : """Generates a new page in the PDF document.""" extractonly = { "username" : entry.Name } if start : extractonly["start"] = start if end : extractonly["end"] = end records = self.storage.extractHistory(extractonly) amount = vatamount = 0.0 vatamount = 0.0 if records : self.canvas.doForm("background") self.ypos = self.yorigine - (cm + 20) records = self.summarizeDatas(records, "history", extractonly, True) fieldnames = records[0] fields = {} for i in range(len(fieldnames)) : fields[fieldnames[i]] = i numberofbytes = records[1][fields["jobsizebytes"]] numberofpages = records[1][fields["jobsize"]] amount = records[1][fields["jobprice"]] if amount > 0.0 : # There's something due ! ht = ((amount * 10000.0) / (100.0 + vat)) / 100.0 vatamount = amount - ht self.printVar(_("Invoice"), "#%s" % invoicenumber, 22) self.printVar(_("Username"), entry.Name, 22) self.ypos -= 20 if start : self.printVar(_("Since"), start, 14) if end : self.printVar(_("Until"), end, 14) self.printVar(_("Edited on"), time.strftime("%c", time.localtime()), 14) self.ypos -= 20 # self.printVar(_("Number of bytes"), str(numberofbytes), 14) self.printVar(_("Number of pages printed"), str(numberofpages), 14) self.ypos -= 20 self.printVar(_("Amount due"), "%.2f %s" % (amount, unitname), 22) if vat : self.ypos += 8 self.printVar("%s (%.2f%%)" % (_("Included VAT"), vat), "%.2f %s" % (vatamount, unitname), 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(pwd.getpwuid(os.geteuid())[0]) 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 : 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) self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % _("Here's the invoice for your printouts")) 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 main(self, names, 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" if not self.config.isAdmin : raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command.")) try : vat = float(options["vat"]) if not (0.0 <= vat < 100.0) : raise ValueError except : raise PyKotaCommandLineError, _("Incorrect value '%s' for the --vat command line option") % options["vat"] try : firstnumber = number = int(options["number"]) if number <= 0 : raise ValueError except : raise PyKotaCommandLineError, _("Incorrect value '%s' for the --number command line option") % options["number"] self.pagesize = self.getPageSize(options["pagesize"]) if self.pagesize is None : self.pagesize = self.getPageSize("a4") self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn") if not names : names = [ "*" ] percent = Percent(self) outfname = options["output"] if outfname != "-" : percent.display("%s..." % _("Extracting datas")) entries = self.storage.getMatchingUsers(",".join(names)) percent.setSize(len(entries)) if entries : percent.display("\n%s\n" % _("Generating invoices")) self.initPDF(options["logo"].strip()) for entry in entries : number += self.pagePDF(number, entry, vat, options["start"], options["end"], options["unit"]) if outfname != "-" : percent.oneMore() if number > firstnumber : self.endPDF(outfname) if outfname != "-" : percent.done() if __name__ == "__main__" : retcode = 0 try : defaults = { "vat" : "0.0", "unit" : N_("Credits"), "output" : "-", "pagesize" : "a4", \ "logo" : "/usr/share/pykota/logos/pykota.jpeg", "number" : "1", } short_options = "vho:r:u:V:p:l:n:s:e:" long_options = ["help", "version", "start=", "end=", \ "reference=", "unit=", "output=", \ "pagesize=", "logo=", "vat=", "number="] # Initializes the command line tool invoiceGenerator = PKInvoice(doc=__doc__) invoiceGenerator.deferredInit() # parse and checks the command line (options, args) = invoiceGenerator.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=True) # sets long options options["help"] = options["h"] or options["help"] options["version"] = options["v"] or options["version"] options["start"] = options["s"] or options["start"] options["end"] = options["e"] or options["end"] options["vat"] = options["V"] or options["vat"] or defaults["vat"] options["unit"] = options["u"] or options["unit"] or defaults["unit"] options["output"] = options["o"] or options["output"] or defaults["output"] options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"] options["number"] = options["n"] or options["number"] or defaults["number"] options["logo"] = options["l"] or options["logo"] if options["logo"] is None : # Allows --logo="" to disable the logo entirely options["logo"] = defaults["logo"] if options["help"] : invoiceGenerator.display_usage_and_quit() elif options["version"] : invoiceGenerator.display_version_and_quit() else : retcode = invoiceGenerator.main(args, options) except KeyboardInterrupt : sys.stderr.write("\nInterrupted with Ctrl+C !\n") retcode = -3 except PyKotaCommandLineError, msg : sys.stderr.write("%s : %s\n" % (sys.argv[0], msg)) retcode = -2 except SystemExit : pass except : try : invoiceGenerator.crashed("pkinvoice failed") except : crashed("pkinvoice failed") retcode = -1 try : invoiceGenerator.storage.close() except (TypeError, NameError, AttributeError) : pass sys.exit(retcode)