root / pykota / trunk / bin / pkinvoice @ 2937

Revision 2811, 14.4 kB (checked in by jerome, 18 years ago)

Incorrect docstring.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota Invoice generator
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import sys
28import os
29import pwd
30import time
31import cStringIO
32
33try :
34    from reportlab.pdfgen import canvas
35    from reportlab.lib import pagesizes
36    from reportlab.lib.units import cm
37except ImportError :   
38    hasRL = 0
39else :   
40    hasRL = 1
41   
42try :
43    import PIL.Image 
44except ImportError :   
45    hasPIL = 0
46else :   
47    hasPIL = 1
48
49from pykota.tool import Percent, PyKotaToolError, PyKotaCommandLineError, crashed, N_
50from pykota.dumper import DumPyKota
51
52__doc__ = N_("""pkinvoice v%(__version__)s (c) %(__years__)s %(__author__)s
53
54An invoice generator for PyKota.
55
56command line usage :
57
58  pkinvoice [options] user1 user2 ... userN
59
60options :
61
62  -v | --version       Prints pkinvoice's version number then exits.
63  -h | --help          Prints this message then exits.
64 
65  -l | --logo img      Use the image as the invoice's logo. The logo will
66                       be drawn at the center top of the page. The default
67                       logo is /usr/share/pykota/logos/pykota.jpeg
68                       
69  -p | --pagesize sz   Sets sz as the page size. Most well known
70                       page sizes are recognized, like 'A4' or 'Letter'
71                       to name a few. The default size is A4.
72                       
73  -n | --number N      Sets the number of the first invoice. This number
74                       will automatically be incremented for each invoice.
75                       
76  -o | --output f.pdf  Defines the name of the invoice file which will
77                       be generated as a PDF document. If not set or
78                       set to '-', the PDF document is sent to standard
79                       output.
80                       
81  -u | --unit u        Defines the name of the unit to use on the invoice.                       
82                       The default unit is 'Credits', optionally translated
83                       to your native language if it is supported by PyKota.
84 
85  -V | --vat p         Sets the percent value of the applicable VAT to be
86                       exposed. The default is 0.0, meaning no VAT
87                       information will be included.
88                       
89  -s | --start date    Sets the starting date for the print jobs invoiced.
90 
91  -e | --end date      Sets the ending date for the print jobs invoiced.
92                       
93  user1 through userN can use wildcards if needed. If no user argument is
94  used, a wildcard of '*' is assumed, meaning include all users.
95 
96  Dates formating with --start and --end :
97 
98    YYYY : year boundaries
99    YYYYMM : month boundaries
100    YYYYMMDD : day boundaries
101    YYYYMMDDhh : hour boundaries
102    YYYYMMDDhhmm : minute boundaries
103    YYYYMMDDhhmmss : second boundaries
104    yesterday[+-NbDays] : yesterday more or less N days (e.g. : yesterday-15)
105    today[+-NbDays] : today more or less N days (e.g. : today-15)
106    tomorrow[+-NbDays] : tomorrow more or less N days (e.g. : tomorrow-15)
107    now[+-NbDays] : now more or less N days (e.g. now-15)
108
109  'now' and 'today' are not exactly the same since today represents the first
110  or last second of the day depending on if it's used in a start= or end=
111  date expression. The utility to be able to specify dates in the future is
112  a question which remains to be answered :-)
113                                       
114examples :                       
115
116  $ pkinvoice --unit EURO --output invoices.pdf --start=now-30
117 
118  Will generate a PDF document containing invoices for all users
119  who have spent some credits last month. Invoices will be done in
120  EURO.  No VAT information will be included.
121""") 
122       
123class PKInvoice(DumPyKota) :       
124    """A class for pkinvoice."""
125    def getPageSize(self, pgsize) :
126        """Returns the correct page size or None if not found."""
127        try :
128            return getattr(pagesizes, pgsize.upper())
129        except AttributeError :   
130            try :
131                return getattr(pagesizes, pgsize.lower())
132            except AttributeError :
133                pass
134               
135    def printVar(self, label, value, size) :
136        """Outputs a variable onto the PDF canvas.
137       
138           Returns the number of points to substract to current Y coordinate.
139        """   
140        xcenter = (self.pagesize[0] / 2.0) - 1*cm
141        self.canvas.saveState()
142        self.canvas.setFont("Helvetica-Bold", size)
143        self.canvas.setFillColorRGB(0, 0, 0)
144        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label)
145        self.canvas.setFont("Courier-Bold", size)
146        self.canvas.setFillColorRGB(0, 0, 1)
147        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value)
148        self.canvas.restoreState()
149        self.ypos -= (size + 4)
150       
151    def pagePDF(self, invoicenumber, entry, vat, start, end, unitname) :
152        """Generates a new page in the PDF document."""
153        extractonly = { "username" : entry.Name }
154        if start :
155            extractonly["start"] = start
156        if end :   
157            extractonly["end"] = end
158        records = self.storage.extractHistory(extractonly)
159        amount = vatamount = 0.0
160        vatamount = 0.0
161        if records :
162            self.canvas.doForm("background")
163            self.ypos = self.yorigine - (cm + 20)
164            records = self.summarizeDatas(records, "history", extractonly, True)
165            fieldnames = records[0]
166            fields = {}
167            for i in range(len(fieldnames)) :
168                fields[fieldnames[i]] = i
169            numberofbytes = records[1][fields["jobsizebytes"]]
170            numberofpages = records[1][fields["jobsize"]]
171            amount = records[1][fields["jobprice"]]
172            if amount > 0.0 :
173                # There's something due !
174                ht = ((amount * 10000.0) / (100.0 + vat)) / 100.0
175                vatamount = amount - ht
176                self.printVar(_("Invoice"), "#%s" % invoicenumber, 22)
177                self.printVar(_("Username"), entry.Name, 22)
178                self.ypos -= 20
179                if start : 
180                    self.printVar(_("Since"), start, 14)
181                if end :
182                    self.printVar(_("Until"), end, 14)
183                self.printVar(_("Edited on"), time.strftime("%c", time.localtime()), 14)
184                   
185                self.ypos -= 20
186                # self.printVar(_("Number of bytes"), str(numberofbytes), 14)
187                self.printVar(_("Number of pages printed"), str(numberofpages), 14)
188                self.ypos -= 20
189                self.printVar(_("Amount due"), "%.2f %s" % (amount, unitname), 22)
190                if vat :
191                    self.ypos += 8
192                    self.printVar("%s (%.2f%%)" % (_("Included VAT"), vat), "%.2f %s" % (vatamount, unitname), 14)
193                self.canvas.showPage()
194                return 1
195        return 0   
196       
197    def initPDF(self, logo) :
198        """Initializes the PDF document."""
199        self.pdfDocument = cStringIO.StringIO()       
200        self.canvas = c = canvas.Canvas(self.pdfDocument, \
201                                        pagesize=self.pagesize, \
202                                        pageCompression=1)
203       
204        c.setAuthor(pwd.getpwuid(os.geteuid())[0])
205        c.setTitle("PyKota invoices")
206        c.setSubject("Invoices generated with PyKota")
207       
208       
209        self.canvas.beginForm("background")
210        self.canvas.saveState()
211       
212        self.ypos = self.pagesize[1] - (2 * cm)           
213       
214        xcenter = self.pagesize[0] / 2.0
215        if logo :
216            try :   
217                imglogo = PIL.Image.open(logo)
218            except :   
219                self.printInfo("Unable to open image %s" % logo, "warn")
220            else :
221                (width, height) = imglogo.size
222                multi = float(width) / (8 * cm) 
223                width = float(width) / multi
224                height = float(height) / multi
225                self.ypos -= height
226                c.drawImage(logo, xcenter - (width / 2.0), \
227                                  self.ypos, \
228                                  width, height)
229       
230        self.ypos -= (cm + 20)
231        self.canvas.setFont("Helvetica-Bold", 14)
232        self.canvas.setFillColorRGB(0, 0, 0)
233        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % _("Here's the invoice for your printouts"))
234       
235        self.yorigine = self.ypos
236        self.canvas.restoreState()
237        self.canvas.endForm()
238       
239    def endPDF(self, fname) :   
240        """Flushes the PDF generator."""
241        self.canvas.save()
242        if fname != "-" :       
243            outfile = open(fname, "w")
244            outfile.write(self.pdfDocument.getvalue())
245            outfile.close()
246        else :   
247            sys.stdout.write(self.pdfDocument.getvalue())
248            sys.stdout.flush()
249       
250    def main(self, names, options) :
251        """Generate invoices."""
252        if not hasRL :
253            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
254        if not hasPIL :
255            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
256           
257        if not self.config.isAdmin :
258            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
259       
260        try :   
261            vat = float(options["vat"])
262            if not (0.0 <= vat < 100.0) :
263                raise ValueError
264        except :   
265            raise PyKotaCommandLineError, _("Incorrect value '%s' for the --vat command line option") % options["vat"]
266           
267        try :   
268            firstnumber = number = int(options["number"])
269            if number <= 0 :
270                raise ValueError
271        except :   
272            raise PyKotaCommandLineError, _("Incorrect value '%s' for the --number command line option") % options["number"]
273           
274        self.pagesize = self.getPageSize(options["pagesize"])
275        if self.pagesize is None :
276            self.pagesize = self.getPageSize("a4")
277            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
278           
279        if not names :
280            names = [ "*" ]
281           
282        percent = Percent(self)
283        outfname = options["output"]   
284        if outfname != "-" :
285            percent.display("%s..." % _("Extracting datas"))
286        entries = self.storage.getMatchingUsers(",".join(names))
287        percent.setSize(len(entries))
288        if entries :
289            percent.display("\n%s\n" % _("Generating invoices"))
290            self.initPDF(options["logo"].strip())
291            for entry in entries :
292                number += self.pagePDF(number, entry, vat, options["start"], options["end"], options["unit"])
293                if outfname != "-" :
294                    percent.oneMore()
295                   
296            if number > firstnumber :
297                self.endPDF(outfname)
298           
299        if outfname != "-" :
300            percent.done()
301                     
302if __name__ == "__main__" : 
303    retcode = 0
304    try :
305        defaults = { "vat" : "0.0",
306                     "unit" : N_("Credits"),
307                     "output" : "-",
308                     "pagesize" : "a4", \
309                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
310                     "number" : "1",
311                   }
312        short_options = "vho:r:u:V:p:l:n:s:e:"
313        long_options = ["help", "version", "start=", "end=", \
314                        "reference=", "unit=", "output=", \
315                        "pagesize=", "logo=", "vat=", "number="]
316       
317        # Initializes the command line tool
318        invoiceGenerator = PKInvoice(doc=__doc__)
319        invoiceGenerator.deferredInit()
320       
321        # parse and checks the command line
322        (options, args) = invoiceGenerator.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=True)
323       
324        # sets long options
325        options["help"] = options["h"] or options["help"]
326        options["version"] = options["v"] or options["version"]
327       
328        options["start"] = options["s"] or options["start"]
329        options["end"] = options["e"] or options["end"]
330        options["vat"] = options["V"] or options["vat"] or defaults["vat"]
331        options["unit"] = options["u"] or options["unit"] or defaults["unit"]
332        options["output"] = options["o"] or options["output"] or defaults["output"]
333        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
334        options["number"] = options["n"] or options["number"] or defaults["number"]
335        options["logo"] = options["l"] or options["logo"]
336        if options["logo"] is None : # Allows --logo="" to disable the logo entirely
337            options["logo"] = defaults["logo"] 
338       
339        if options["help"] :
340            invoiceGenerator.display_usage_and_quit()
341        elif options["version"] :
342            invoiceGenerator.display_version_and_quit()
343        else :
344            retcode = invoiceGenerator.main(args, options)
345    except KeyboardInterrupt :       
346        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
347        retcode = -3
348    except PyKotaCommandLineError, msg :     
349        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
350        retcode = -2
351    except SystemExit :       
352        pass
353    except :
354        try :
355            invoiceGenerator.crashed("pkinvoice failed")
356        except :   
357            crashed("pkinvoice failed")
358        retcode = -1
359
360    try :
361        invoiceGenerator.storage.close()
362    except (TypeError, NameError, AttributeError) :   
363        pass
364       
365    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.