root / pykota / trunk / bin / pkinvoice @ 2772

Revision 2689, 14.5 kB (checked in by jerome, 19 years ago)

Doesn't output an invoice when amount due is 0.0.

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