root / pykota / trunk / bin / pkinvoice @ 3434

Revision 3434, 13.1 kB (checked in by jerome, 16 years ago)

Moved the progress report code to its own module.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[2660]1#! /usr/bin/env python
[3411]2# -*- coding: utf-8 -*-*-
[2660]3#
[3260]4# PyKota : Print Quotas for CUPS
[2660]5#
[3275]6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
[3260]7# This program is free software: you can redistribute it and/or modify
[2660]8# it under the terms of the GNU General Public License as published by
[3260]9# the Free Software Foundation, either version 3 of the License, or
[2660]10# (at your option) any later version.
[3413]11#
[2660]12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
[3413]16#
[2660]17# You should have received a copy of the GNU General Public License
[3260]18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[2660]19#
20# $Id$
21#
22#
23
[3341]24"""An invoice generator for PyKota"""
25
[2660]26import sys
27import os
28import pwd
[2668]29import time
[2663]30import cStringIO
31
32try :
33    from reportlab.pdfgen import canvas
34    from reportlab.lib import pagesizes
35    from reportlab.lib.units import cm
[3413]36except ImportError :
[3288]37    hasRL = False
[3413]38else :
[3288]39    hasRL = True
[3413]40
[2663]41try :
[3413]42    import PIL.Image
43except ImportError :
[3288]44    hasPIL = False
[3413]45else :
[3288]46    hasPIL = True
[2663]47
[3294]48import pykota.appinit
[3341]49from pykota.utils import run
50from pykota.commandline import PyKotaOptionParser, \
51                               checkandset_pagesize, \
52                               checkandset_positiveint, \
53                               checkandset_percent
54from pykota.pdfutils import getPageSize
[3288]55from pykota.errors import PyKotaToolError, PyKotaCommandLineError
[3434]56from pykota.tool import PyKotaTool
57from pykota.progressbar import Percent
[2660]58
[3413]59class PKInvoice(PyKotaTool) :
[3090]60    """A class for invoice generator."""
61    validfilterkeys = [ "username",
62                        "printername",
63                        "hostname",
64                        "jobid",
65                        "billingcode",
66                        "start",
67                        "end",
68                      ]
[3413]69
[2668]70    def printVar(self, label, value, size) :
71        """Outputs a variable onto the PDF canvas.
[3413]72
[2668]73           Returns the number of points to substract to current Y coordinate.
[3413]74        """
[2668]75        xcenter = (self.pagesize[0] / 2.0) - 1*cm
76        self.canvas.saveState()
77        self.canvas.setFont("Helvetica-Bold", size)
78        self.canvas.setFillColorRGB(0, 0, 0)
[3303]79        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label)
[2668]80        self.canvas.setFont("Courier-Bold", size)
81        self.canvas.setFillColorRGB(0, 0, 1)
[3303]82        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value)
[2668]83        self.canvas.restoreState()
84        self.ypos -= (size + 4)
[3413]85
[3090]86    def pagePDF(self, invoicenumber, name, values, unit, vat) :
[2663]87        """Generates a new page in the PDF document."""
[3090]88        amount = values["nbcredits"]
89        if amount : # is there's something due ?
90            ht = ((amount * 10000.0) / (100.0 + vat)) / 100.0
91            vatamount = amount - ht
[2668]92            self.canvas.doForm("background")
93            self.ypos = self.yorigine - (cm + 20)
[3090]94            self.printVar(_("Invoice"), "#%s" % invoicenumber, 22)
95            self.printVar(_("Username"), name, 22)
96            self.ypos -= 20
[3341]97            datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
98            self.printVar(_("Edited on"), datetime, 14)
[3413]99
[3090]100            self.ypos -= 20
101            self.printVar(_("Number of jobs printed"), str(values["nbjobs"]), 18)
102            self.printVar(_("Number of pages printed"), str(values["nbpages"]), 18)
103            self.ypos -= 20
104            self.printVar(_("Amount due"), "%.3f %s" % (amount, unit), 18)
105            if vat :
106                self.ypos += 8
107                self.printVar("%s (%.2f%%)" % (_("Included VAT"), vat), "%.3f %s" % (vatamount, unit), 14)
108            self.canvas.showPage()
109            return 1
[3413]110        return 0
111
[2666]112    def initPDF(self, logo) :
[2663]113        """Initializes the PDF document."""
[3413]114        self.pdfDocument = cStringIO.StringIO()
[2666]115        self.canvas = c = canvas.Canvas(self.pdfDocument, \
116                                        pagesize=self.pagesize, \
117                                        pageCompression=1)
[3413]118
[3276]119        c.setAuthor(self.effectiveUserName)
[3303]120        c.setTitle(_("PyKota invoices"))
121        c.setSubject(_("Invoices generated with PyKota"))
[3413]122
[2663]123        self.canvas.beginForm("background")
124        self.canvas.saveState()
[3413]125
126        self.ypos = self.pagesize[1] - (2 * cm)
127
[2668]128        xcenter = self.pagesize[0] / 2.0
[2663]129        if logo :
[3413]130            try :
[2663]131                imglogo = PIL.Image.open(logo)
[3413]132            except IOError :
[2663]133                self.printInfo("Unable to open image %s" % logo, "warn")
134            else :
135                (width, height) = imglogo.size
[3413]136                multi = float(width) / (8 * cm)
[2663]137                width = float(width) / multi
138                height = float(height) / multi
[2668]139                self.ypos -= height
140                c.drawImage(logo, xcenter - (width / 2.0), \
141                                  self.ypos, \
142                                  width, height)
[3413]143
[2668]144        self.ypos -= (cm + 20)
145        self.canvas.setFont("Helvetica-Bold", 14)
146        self.canvas.setFillColorRGB(0, 0, 0)
[3079]147        msg = _("Here's the invoice for your printouts")
[3303]148        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
[3413]149
[2668]150        self.yorigine = self.ypos
[2663]151        self.canvas.restoreState()
152        self.canvas.endForm()
[3413]153
154    def endPDF(self, fname) :
[2663]155        """Flushes the PDF generator."""
156        self.canvas.save()
[3413]157        if fname != "-" :
[2663]158            outfile = open(fname, "w")
159            outfile.write(self.pdfDocument.getvalue())
160            outfile.close()
[3413]161        else :
[2663]162            sys.stdout.write(self.pdfDocument.getvalue())
163            sys.stdout.flush()
[3413]164
[3090]165    def genInvoices(self, peruser, logo, outfname, firstnumber, unit, vat) :
166        """Generates the invoices file."""
167        if len(peruser) :
[3346]168            percent = Percent(self, size=len(peruser))
[3090]169            if outfname != "-" :
170                percent.display("%s...\n" % _("Generating invoices"))
[3413]171
[3090]172            self.initPDF(logo)
173            number = firstnumber
174            for (name, values) in peruser.items() :
175                number += self.pagePDF(number, name, values, unit, vat)
176                if outfname != "-" :
177                    percent.oneMore()
[3413]178
[3090]179            if number > firstnumber :
180                self.endPDF(outfname)
[3413]181
[3090]182            if outfname != "-" :
183                percent.done()
[3413]184
[3090]185    def main(self, arguments, options) :
[2660]186        """Generate invoices."""
[2663]187        if not hasRL :
188            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
189        if not hasPIL :
190            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
[3413]191
[3367]192        self.adminOnly()
[3413]193
[3341]194        self.pagesize = getPageSize(options.pagesize)
[3413]195
[3090]196        extractonly = {}
197        for filterexp in arguments :
198            if filterexp.strip() :
199                try :
200                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
201                    filterkey = filterkey.lower()
202                    if filterkey not in self.validfilterkeys :
[3413]203                        raise ValueError
204                except ValueError :
[3090]205                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
[3413]206                else :
[3090]207                    extractonly.update({ filterkey : filtervalue })
[3413]208
[2790]209        percent = Percent(self)
[3341]210        outfname = options.output.strip().encode(sys.getfilesystemencoding())
[2661]211        if outfname != "-" :
[2790]212            percent.display("%s..." % _("Extracting datas"))
[3413]213
214        username = extractonly.get("username")
[3090]215        if username :
216            user = self.storage.getUser(username)
217        else :
218            user = None
[3413]219
220        printername = extractonly.get("printername")
[3090]221        if printername :
222            printer = self.storage.getPrinter(printername)
[3413]223        else :
[3090]224            printer = None
[3413]225
[3090]226        start = extractonly.get("start")
227        end = extractonly.get("end")
228        (start, end) = self.storage.cleanDates(start, end)
[3413]229
230        jobs = self.storage.retrieveHistory(user=user,
231                                            printer=printer,
[3090]232                                            hostname=extractonly.get("hostname"),
233                                            billingcode=extractonly.get("billingcode"),
234                                            jobid=extractonly.get("jobid"),
235                                            start=start,
236                                            end=end,
237                                            limit=0)
[3413]238
239        peruser = {}
240        nbjobs = 0
241        nbpages = 0
[3090]242        nbcredits = 0.0
243        percent.setSize(len(jobs))
244        if outfname != "-" :
245            percent.display("\n")
[3413]246        for job in jobs :
[3090]247            if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
248                nbpages += job.JobSize
249                nbcredits += job.JobPrice
250                counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
251                counters["nbpages"] += job.JobSize
252                counters["nbcredits"] += job.JobPrice
253                counters["nbjobs"] += 1
254                nbjobs += 1
[2663]255                if outfname != "-" :
[2790]256                    percent.oneMore()
[2663]257        if outfname != "-" :
[2790]258            percent.done()
[3413]259        self.genInvoices(peruser,
260                         options.logo.strip().encode(sys.getfilesystemencoding()),
261                         outfname,
262                         options.number,
263                         options.unit or _("Credits"),
[3341]264                         options.vat)
[3413]265        if outfname != "-" :
[3346]266            nbusers = len(peruser)
[3429]267            self.display("%s\n" % (_("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
268                     % locals()))
[3413]269
270if __name__ == "__main__" :
[3341]271    parser = PyKotaOptionParser(description=_("Invoice generator for PyKota."),
272                                usage="pkinvoice [options] [filterexpr]")
273    parser.add_option("-l", "--logo",
274                            dest="logo",
275                            default=u"/usr/share/pykota/logos/pykota.jpeg",
[3360]276                            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."))
[3341]277    parser.add_option("-p", "--pagesize",
278                            type="string",
279                            action="callback",
280                            callback=checkandset_pagesize,
281                            dest="pagesize",
282                            default=u"A4",
[3360]283                            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."))
[3413]284    parser.add_option("-n", "--number",
[3341]285                            dest="number",
286                            type="int",
287                            action="callback",
288                            callback=checkandset_positiveint,
289                            default=1,
[3365]290                            help=_("Set the number of the first invoice. This number will automatically be incremented for each invoice. The default value is %default."))
[3341]291    parser.add_option("-o", "--output",
292                            dest="output",
293                            type="string",
294                            default=u"-",
295                            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."))
[3413]296
[3341]297    # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861
298    # TODO : we can't use 'default=_("Credits")' for this option
[3413]299    parser.add_option("-u", "--unit",
[3341]300                            dest="unit",
301                            type="string",
302                            help=_("The name of the unit to use on the invoices. The default value is 'Credits' or its locale translation."))
[3413]303
304    parser.add_option("-V", "--vat",
[3341]305                            dest="vat",
306                            type="float",
307                            action="callback",
308                            callback=checkandset_percent,
309                            default=0.0,
310                            help=_("The value in percent of the applicable VAT to be exposed. The default is %default, meaning no VAT."))
[3413]311
312    parser.add_filterexpression("username", _("User's name"))
313    parser.add_filterexpression("printername", _("Printer's name"))
314    parser.add_filterexpression("hostname", _("Host's name"))
315    parser.add_filterexpression("jobid", _("Job's id"))
[3341]316    parser.add_filterexpression("billingcode", _("Job's billing code"))
[3413]317    parser.add_filterexpression("start", _("Job's date of printing"))
318    parser.add_filterexpression("end", _("Job's date of printing"))
319
320    parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30',
[3341]321                       _("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."))
[3413]322
323    run(parser, PKInvoice)
Note: See TracBrowser for help on using the browser.