root / pykota / trunk / bin / pkinvoice @ 2666

Revision 2666, 12.8 kB (checked in by jerome, 18 years ago)

Revamped the functionnality completely, to base invoices on the printing
history instead of on the account balance : this way we can use date
based filtering ala dumpykota, and we also have additionnal informations.

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