root / pykota / trunk / bin / pkinvoice @ 3083

Revision 3083, 14.4 kB (checked in by jerome, 17 years ago)

Changed output format for amounts.

  • 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 formatting 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 :" % self.userCharsetToUTF8(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, self.userCharsetToUTF8(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"), "%.3f %s" % (amount, unitname), 22)
190                if vat :
191                    self.ypos += 8
192                    self.printVar("%s (%.2f%%)" % (_("Included VAT"), vat), "%.3f %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        msg = _("Here's the invoice for your printouts")
234        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % self.userCharsetToUTF8(msg))
235       
236        self.yorigine = self.ypos
237        self.canvas.restoreState()
238        self.canvas.endForm()
239       
240    def endPDF(self, fname) :   
241        """Flushes the PDF generator."""
242        self.canvas.save()
243        if fname != "-" :       
244            outfile = open(fname, "w")
245            outfile.write(self.pdfDocument.getvalue())
246            outfile.close()
247        else :   
248            sys.stdout.write(self.pdfDocument.getvalue())
249            sys.stdout.flush()
250       
251    def main(self, names, options) :
252        """Generate invoices."""
253        if not hasRL :
254            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
255        if not hasPIL :
256            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
257           
258        if not self.config.isAdmin :
259            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
260       
261        try :   
262            vat = float(options["vat"])
263            if not (0.0 <= vat < 100.0) :
264                raise ValueError
265        except :   
266            raise PyKotaCommandLineError, _("Incorrect value '%s' for the --vat command line option") % options["vat"]
267           
268        try :   
269            firstnumber = number = int(options["number"])
270            if number <= 0 :
271                raise ValueError
272        except :   
273            raise PyKotaCommandLineError, _("Incorrect value '%s' for the --number command line option") % options["number"]
274           
275        self.pagesize = self.getPageSize(options["pagesize"])
276        if self.pagesize is None :
277            self.pagesize = self.getPageSize("a4")
278            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
279           
280        if not names :
281            names = [ "*" ]
282           
283        percent = Percent(self)
284        outfname = options["output"]   
285        if outfname != "-" :
286            percent.display("%s..." % _("Extracting datas"))
287        entries = self.storage.getMatchingUsers(",".join(names))
288        percent.setSize(len(entries))
289        if entries :
290            percent.display("\n%s\n" % _("Generating invoices"))
291            self.initPDF(options["logo"].strip())
292            for entry in entries :
293                number += self.pagePDF(number, entry, vat, options["start"], options["end"], options["unit"])
294                if outfname != "-" :
295                    percent.oneMore()
296                   
297            if number > firstnumber :
298                self.endPDF(outfname)
299           
300        if outfname != "-" :
301            percent.done()
302                     
303if __name__ == "__main__" : 
304    retcode = 0
305    try :
306        defaults = { "vat" : "0.0",
307                     "unit" : N_("Credits"),
308                     "output" : "-",
309                     "pagesize" : "a4", \
310                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
311                     "number" : "1",
312                   }
313        short_options = "vho:u:V:p:l:n:s:e:"
314        long_options = ["help", "version", "start=", "end=", \
315                        "unit=", "output=", \
316                        "pagesize=", "logo=", "vat=", "number="]
317       
318        # Initializes the command line tool
319        invoiceGenerator = PKInvoice(doc=__doc__)
320        invoiceGenerator.deferredInit()
321       
322        # parse and checks the command line
323        (options, args) = invoiceGenerator.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=True)
324       
325        # sets long options
326        options["help"] = options["h"] or options["help"]
327        options["version"] = options["v"] or options["version"]
328       
329        options["start"] = options["s"] or options["start"]
330        options["end"] = options["e"] or options["end"]
331        options["vat"] = options["V"] or options["vat"] or defaults["vat"]
332        options["unit"] = options["u"] or options["unit"] or defaults["unit"]
333        options["output"] = options["o"] or options["output"] or defaults["output"]
334        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
335        options["number"] = options["n"] or options["number"] or defaults["number"]
336        options["logo"] = options["l"] or options["logo"]
337        if options["logo"] is None : # Allows --logo="" to disable the logo entirely
338            options["logo"] = defaults["logo"] 
339       
340        if options["help"] :
341            invoiceGenerator.display_usage_and_quit()
342        elif options["version"] :
343            invoiceGenerator.display_version_and_quit()
344        else :
345            retcode = invoiceGenerator.main(args, options)
346    except KeyboardInterrupt :       
347        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
348        retcode = -3
349    except PyKotaCommandLineError, msg :     
350        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
351        retcode = -2
352    except SystemExit :       
353        pass
354    except :
355        try :
356            invoiceGenerator.crashed("pkinvoice failed")
357        except :   
358            crashed("pkinvoice failed")
359        retcode = -1
360
361    try :
362        invoiceGenerator.storage.close()
363    except (TypeError, NameError, AttributeError) :   
364        pass
365       
366    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.