root / pykota / trunk / bin / pkinvoice @ 3341

Revision 3341, 14.0 kB (checked in by jerome, 16 years ago)

pkinvoice now works new style.

  • 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)
168            percent.setSize(len(peruser))
169            if outfname != "-" :
170                percent.display("%s...\n" % _("Generating invoices"))
171               
172            self.initPDF(logo)
173            number = firstnumber
174            for (name, values) in peruser.items() :
175                number += self.pagePDF(number, name, values, unit, vat)
176                if outfname != "-" :
177                    percent.oneMore()
178                   
179            if number > firstnumber :
180                self.endPDF(outfname)
181               
182            if outfname != "-" :
183                percent.done()
184       
185    def main(self, arguments, options) :
186        """Generate invoices."""
187        if not hasRL :
188            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
189        if not hasPIL :
190            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
191           
192        if not self.config.isAdmin :
193            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
194       
195        self.pagesize = getPageSize(options.pagesize)
196           
197        extractonly = {}
198        for filterexp in arguments :
199            if filterexp.strip() :
200                try :
201                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
202                    filterkey = filterkey.lower()
203                    if filterkey not in self.validfilterkeys :
204                        raise ValueError               
205                except ValueError :   
206                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
207                else :   
208                    extractonly.update({ filterkey : filtervalue })
209           
210        percent = Percent(self)
211        outfname = options.output.strip().encode(sys.getfilesystemencoding())
212        if outfname != "-" :
213            percent.display("%s..." % _("Extracting datas"))
214           
215        username = extractonly.get("username")   
216        if username :
217            user = self.storage.getUser(username)
218        else :
219            user = None
220           
221        printername = extractonly.get("printername")   
222        if printername :
223            printer = self.storage.getPrinter(printername)
224        else :   
225            printer = None
226           
227        start = extractonly.get("start")
228        end = extractonly.get("end")
229        (start, end) = self.storage.cleanDates(start, end)
230       
231        jobs = self.storage.retrieveHistory(user=user,   
232                                            printer=printer, 
233                                            hostname=extractonly.get("hostname"),
234                                            billingcode=extractonly.get("billingcode"),
235                                            jobid=extractonly.get("jobid"),
236                                            start=start,
237                                            end=end,
238                                            limit=0)
239           
240        peruser = {}                                   
241        nbjobs = 0                                   
242        nbpages = 0                                           
243        nbcredits = 0.0
244        percent.setSize(len(jobs))
245        if outfname != "-" :
246            percent.display("\n")
247        for job in jobs :                                   
248            if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
249                nbpages += job.JobSize
250                nbcredits += job.JobPrice
251                counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
252                counters["nbpages"] += job.JobSize
253                counters["nbcredits"] += job.JobPrice
254                counters["nbjobs"] += 1
255                nbjobs += 1
256                if outfname != "-" :
257                    percent.oneMore()
258        if outfname != "-" :
259            percent.done()
260        self.genInvoices(peruser, 
261                         options.logo.strip().encode(sys.getfilesystemencoding()), 
262                         outfname, 
263                         options.number, 
264                         options.unit or _("Credits"), 
265                         options.vat)
266        if outfname != "-" :   
267            print _("Invoiced %i users for %i jobs, %i pages and %.3f credits") \
268                % (len(peruser), nbjobs, nbpages, nbcredits)
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=_("Sets 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.