root / pykota / trunk / bin / pkinvoice @ 3365

Revision 3365, 14.1 kB (checked in by jerome, 17 years ago)

Fixed small inconsistency in online help.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: UTF-8 -*-
3#
4# PyKota : Print Quotas for CUPS
5#
6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
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.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22#
23
24"""An invoice generator for PyKota"""
25
26import sys
27import os
28import pwd
29import time
30import cStringIO
31
32try :
33    from reportlab.pdfgen import canvas
34    from reportlab.lib import pagesizes
35    from reportlab.lib.units import cm
36except ImportError :   
37    hasRL = False
38else :   
39    hasRL = True
40   
41try :
42    import PIL.Image 
43except ImportError :   
44    hasPIL = False
45else :   
46    hasPIL = True
47
48import pykota.appinit
49from pykota.utils import run
50from pykota.commandline import PyKotaOptionParser, \
51                               checkandset_pagesize, \
52                               checkandset_positiveint, \
53                               checkandset_percent
54from pykota.pdfutils import getPageSize
55from pykota.errors import PyKotaToolError, PyKotaCommandLineError
56from pykota.tool import Percent, PyKotaTool
57
58class PKInvoice(PyKotaTool) :       
59    """A class for invoice generator."""
60    validfilterkeys = [ "username",
61                        "printername",
62                        "hostname",
63                        "jobid",
64                        "billingcode",
65                        "start",
66                        "end",
67                      ]
68                     
69    def printVar(self, label, value, size) :
70        """Outputs a variable onto the PDF canvas.
71       
72           Returns the number of points to substract to current Y coordinate.
73        """   
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)
78        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label)
79        self.canvas.setFont("Courier-Bold", size)
80        self.canvas.setFillColorRGB(0, 0, 1)
81        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value)
82        self.canvas.restoreState()
83        self.ypos -= (size + 4)
84       
85    def pagePDF(self, invoicenumber, name, values, unit, vat) :
86        """Generates a new page in the PDF document."""
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
91            self.canvas.doForm("background")
92            self.ypos = self.yorigine - (cm + 20)
93            self.printVar(_("Invoice"), "#%s" % invoicenumber, 22)
94            self.printVar(_("Username"), name, 22)
95            self.ypos -= 20
96            datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
97            self.printVar(_("Edited on"), datetime, 14)
98               
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
109        return 0   
110       
111    def initPDF(self, logo) :
112        """Initializes the PDF document."""
113        self.pdfDocument = cStringIO.StringIO()       
114        self.canvas = c = canvas.Canvas(self.pdfDocument, \
115                                        pagesize=self.pagesize, \
116                                        pageCompression=1)
117       
118        c.setAuthor(self.effectiveUserName)
119        c.setTitle(_("PyKota invoices"))
120        c.setSubject(_("Invoices generated with PyKota"))
121       
122        self.canvas.beginForm("background")
123        self.canvas.saveState()
124       
125        self.ypos = self.pagesize[1] - (2 * cm)           
126       
127        xcenter = self.pagesize[0] / 2.0
128        if logo :
129            try :   
130                imglogo = PIL.Image.open(logo)
131            except IOError :   
132                self.printInfo("Unable to open image %s" % logo, "warn")
133            else :
134                (width, height) = imglogo.size
135                multi = float(width) / (8 * cm) 
136                width = float(width) / multi
137                height = float(height) / multi
138                self.ypos -= height
139                c.drawImage(logo, xcenter - (width / 2.0), \
140                                  self.ypos, \
141                                  width, height)
142       
143        self.ypos -= (cm + 20)
144        self.canvas.setFont("Helvetica-Bold", 14)
145        self.canvas.setFillColorRGB(0, 0, 0)
146        msg = _("Here's the invoice for your printouts")
147        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
148       
149        self.yorigine = self.ypos
150        self.canvas.restoreState()
151        self.canvas.endForm()
152       
153    def endPDF(self, fname) :   
154        """Flushes the PDF generator."""
155        self.canvas.save()
156        if fname != "-" :       
157            outfile = open(fname, "w")
158            outfile.write(self.pdfDocument.getvalue())
159            outfile.close()
160        else :   
161            sys.stdout.write(self.pdfDocument.getvalue())
162            sys.stdout.flush()
163       
164    def genInvoices(self, peruser, logo, outfname, firstnumber, unit, vat) :
165        """Generates the invoices file."""
166        if len(peruser) :
167            percent = Percent(self, size=len(peruser))
168            if outfname != "-" :
169                percent.display("%s...\n" % _("Generating invoices"))
170               
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()
177                   
178            if number > firstnumber :
179                self.endPDF(outfname)
180               
181            if outfname != "-" :
182                percent.done()
183       
184    def main(self, arguments, options) :
185        """Generate invoices."""
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"
190           
191        if not self.config.isAdmin :
192            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
193       
194        self.pagesize = getPageSize(options.pagesize)
195           
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 :
203                        raise ValueError               
204                except ValueError :   
205                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
206                else :   
207                    extractonly.update({ filterkey : filtervalue })
208           
209        percent = Percent(self)
210        outfname = options.output.strip().encode(sys.getfilesystemencoding())
211        if outfname != "-" :
212            percent.display("%s..." % _("Extracting datas"))
213           
214        username = extractonly.get("username")   
215        if username :
216            user = self.storage.getUser(username)
217        else :
218            user = None
219           
220        printername = extractonly.get("printername")   
221        if printername :
222            printer = self.storage.getPrinter(printername)
223        else :   
224            printer = None
225           
226        start = extractonly.get("start")
227        end = extractonly.get("end")
228        (start, end) = self.storage.cleanDates(start, end)
229       
230        jobs = self.storage.retrieveHistory(user=user,   
231                                            printer=printer, 
232                                            hostname=extractonly.get("hostname"),
233                                            billingcode=extractonly.get("billingcode"),
234                                            jobid=extractonly.get("jobid"),
235                                            start=start,
236                                            end=end,
237                                            limit=0)
238           
239        peruser = {}                                   
240        nbjobs = 0                                   
241        nbpages = 0                                           
242        nbcredits = 0.0
243        percent.setSize(len(jobs))
244        if outfname != "-" :
245            percent.display("\n")
246        for job in jobs :                                   
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
255                if outfname != "-" :
256                    percent.oneMore()
257        if outfname != "-" :
258            percent.done()
259        self.genInvoices(peruser, 
260                         options.logo.strip().encode(sys.getfilesystemencoding()), 
261                         outfname, 
262                         options.number, 
263                         options.unit or _("Credits"), 
264                         options.vat)
265        if outfname != "-" :   
266            nbusers = len(peruser)
267            print _("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
268                     % locals()
269                     
270if __name__ == "__main__" : 
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",
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."))
277    parser.add_option("-p", "--pagesize",
278                            type="string",
279                            action="callback",
280                            callback=checkandset_pagesize,
281                            dest="pagesize",
282                            default=u"A4",
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."))
284    parser.add_option("-n", "--number",                       
285                            dest="number",
286                            type="int",
287                            action="callback",
288                            callback=checkandset_positiveint,
289                            default=1,
290                            help=_("Set the number of the first invoice. This number will automatically be incremented for each invoice. The default value is %default."))
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."))
296                           
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
299    parser.add_option("-u", "--unit",                   
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."))
303                           
304    parser.add_option("-V", "--vat", 
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."))
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"))                       
316    parser.add_filterexpression("billingcode", _("Job's billing code"))
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', 
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."))
322                       
323    run(parser, PKInvoice)                   
Note: See TracBrowser for help on using the browser.