root / pykota / trunk / bin / pkinvoice @ 3429

Revision 3429, 13.0 kB (checked in by jerome, 16 years ago)

Changed the way informations are output, especially to replace 'print'
statements which won't exist anymore in Python 3.

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