root / pykota / trunk / bin / pkinvoice @ 3413

Revision 3413, 13.0 kB (checked in by jerome, 16 years ago)

Removed unnecessary spaces at EOL.

  • 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, size=len(peruser))
168            if outfname != "-" :
169                percent.display("%s...\n" % _("Generating invoices"))
170
171            self.initPDF(logo)
172            number = firstnumber
173            for (name, values) in peruser.items() :
174                number += self.pagePDF(number, name, values, unit, vat)
175                if outfname != "-" :
176                    percent.oneMore()
177
178            if number > firstnumber :
179                self.endPDF(outfname)
180
181            if outfname != "-" :
182                percent.done()
183
184    def main(self, arguments, options) :
185        """Generate invoices."""
186        if not hasRL :
187            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
188        if not hasPIL :
189            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
190
191        self.adminOnly()
192
193        self.pagesize = getPageSize(options.pagesize)
194
195        extractonly = {}
196        for filterexp in arguments :
197            if filterexp.strip() :
198                try :
199                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
200                    filterkey = filterkey.lower()
201                    if filterkey not in self.validfilterkeys :
202                        raise ValueError
203                except ValueError :
204                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
205                else :
206                    extractonly.update({ filterkey : filtervalue })
207
208        percent = Percent(self)
209        outfname = options.output.strip().encode(sys.getfilesystemencoding())
210        if outfname != "-" :
211            percent.display("%s..." % _("Extracting datas"))
212
213        username = extractonly.get("username")
214        if username :
215            user = self.storage.getUser(username)
216        else :
217            user = None
218
219        printername = extractonly.get("printername")
220        if printername :
221            printer = self.storage.getPrinter(printername)
222        else :
223            printer = None
224
225        start = extractonly.get("start")
226        end = extractonly.get("end")
227        (start, end) = self.storage.cleanDates(start, end)
228
229        jobs = self.storage.retrieveHistory(user=user,
230                                            printer=printer,
231                                            hostname=extractonly.get("hostname"),
232                                            billingcode=extractonly.get("billingcode"),
233                                            jobid=extractonly.get("jobid"),
234                                            start=start,
235                                            end=end,
236                                            limit=0)
237
238        peruser = {}
239        nbjobs = 0
240        nbpages = 0
241        nbcredits = 0.0
242        percent.setSize(len(jobs))
243        if outfname != "-" :
244            percent.display("\n")
245        for job in jobs :
246            if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
247                nbpages += job.JobSize
248                nbcredits += job.JobPrice
249                counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
250                counters["nbpages"] += job.JobSize
251                counters["nbcredits"] += job.JobPrice
252                counters["nbjobs"] += 1
253                nbjobs += 1
254                if outfname != "-" :
255                    percent.oneMore()
256        if outfname != "-" :
257            percent.done()
258        self.genInvoices(peruser,
259                         options.logo.strip().encode(sys.getfilesystemencoding()),
260                         outfname,
261                         options.number,
262                         options.unit or _("Credits"),
263                         options.vat)
264        if outfname != "-" :
265            nbusers = len(peruser)
266            print _("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
267                     % locals()
268
269if __name__ == "__main__" :
270    parser = PyKotaOptionParser(description=_("Invoice generator for PyKota."),
271                                usage="pkinvoice [options] [filterexpr]")
272    parser.add_option("-l", "--logo",
273                            dest="logo",
274                            default=u"/usr/share/pykota/logos/pykota.jpeg",
275                            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."))
276    parser.add_option("-p", "--pagesize",
277                            type="string",
278                            action="callback",
279                            callback=checkandset_pagesize,
280                            dest="pagesize",
281                            default=u"A4",
282                            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."))
283    parser.add_option("-n", "--number",
284                            dest="number",
285                            type="int",
286                            action="callback",
287                            callback=checkandset_positiveint,
288                            default=1,
289                            help=_("Set the number of the first invoice. This number will automatically be incremented for each invoice. The default value is %default."))
290    parser.add_option("-o", "--output",
291                            dest="output",
292                            type="string",
293                            default=u"-",
294                            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."))
295
296    # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861
297    # TODO : we can't use 'default=_("Credits")' for this option
298    parser.add_option("-u", "--unit",
299                            dest="unit",
300                            type="string",
301                            help=_("The name of the unit to use on the invoices. The default value is 'Credits' or its locale translation."))
302
303    parser.add_option("-V", "--vat",
304                            dest="vat",
305                            type="float",
306                            action="callback",
307                            callback=checkandset_percent,
308                            default=0.0,
309                            help=_("The value in percent of the applicable VAT to be exposed. The default is %default, meaning no VAT."))
310
311    parser.add_filterexpression("username", _("User's name"))
312    parser.add_filterexpression("printername", _("Printer's name"))
313    parser.add_filterexpression("hostname", _("Host's name"))
314    parser.add_filterexpression("jobid", _("Job's id"))
315    parser.add_filterexpression("billingcode", _("Job's billing code"))
316    parser.add_filterexpression("start", _("Job's date of printing"))
317    parser.add_filterexpression("end", _("Job's date of printing"))
318
319    parser.add_example('--unit EURO --output /tmp/invoices.pdf start=now-30',
320                       _("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."))
321
322    run(parser, PKInvoice)
Note: See TracBrowser for help on using the browser.