root / pykota / trunk / bin / pkinvoice @ 3561

Revision 3561, 13.0 kB (checked in by jerome, 11 years ago)

Changed copyright years.

  • 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-2013 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 PyKotaTool
57from pykota.progressbar import Percent
58
59class PKInvoice(PyKotaTool) :
60    """A class for invoice generator."""
61    validfilterkeys = [ "username",
62                        "printername",
63                        "hostname",
64                        "jobid",
65                        "billingcode",
66                        "start",
67                        "end",
68                      ]
69
70    def printVar(self, label, value, size) :
71        """Outputs a variable onto the PDF canvas.
72
73           Returns the number of points to substract to current Y coordinate.
74        """
75        xcenter = (self.pagesize[0] / 2.0) - 1*cm
76        self.canvas.saveState()
77        self.canvas.setFont("Helvetica-Bold", size)
78        self.canvas.setFillColorRGB(0, 0, 0)
79        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label)
80        self.canvas.setFont("Courier-Bold", size)
81        self.canvas.setFillColorRGB(0, 0, 1)
82        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value)
83        self.canvas.restoreState()
84        self.ypos -= (size + 4)
85
86    def pagePDF(self, invoicenumber, name, values, unit, vat) :
87        """Generates a new page in the PDF document."""
88        amount = values["nbcredits"]
89        if amount : # is there's something due ?
90            ht = ((amount * 10000.0) / (100.0 + vat)) / 100.0
91            vatamount = amount - ht
92            self.canvas.doForm("background")
93            self.ypos = self.yorigine - (cm + 20)
94            self.printVar(_("Invoice"), "#%s" % invoicenumber, 22)
95            self.printVar(_("Username"), name, 22)
96            self.ypos -= 20
97            datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
98            self.printVar(_("Edited on"), datetime, 14)
99
100            self.ypos -= 20
101            self.printVar(_("Number of jobs printed"), str(values["nbjobs"]), 18)
102            self.printVar(_("Number of pages printed"), str(values["nbpages"]), 18)
103            self.ypos -= 20
104            self.printVar(_("Amount due"), "%.3f %s" % (amount, unit), 18)
105            if vat :
106                self.ypos += 8
107                self.printVar("%s (%.2f%%)" % (_("Included VAT"), vat), "%.3f %s" % (vatamount, unit), 14)
108            self.canvas.showPage()
109            return 1
110        return 0
111
112    def initPDF(self, logo) :
113        """Initializes the PDF document."""
114        self.pdfDocument = cStringIO.StringIO()
115        self.canvas = c = canvas.Canvas(self.pdfDocument, \
116                                        pagesize=self.pagesize, \
117                                        pageCompression=1)
118
119        c.setAuthor(self.effectiveUserName)
120        c.setTitle(_("PyKota invoices"))
121        c.setSubject(_("Invoices generated with PyKota"))
122
123        self.canvas.beginForm("background")
124        self.canvas.saveState()
125
126        self.ypos = self.pagesize[1] - (2 * cm)
127
128        xcenter = self.pagesize[0] / 2.0
129        if logo :
130            try :
131                imglogo = PIL.Image.open(logo)
132            except IOError :
133                self.printInfo("Unable to open image %s" % logo, "warn")
134            else :
135                (width, height) = imglogo.size
136                multi = float(width) / (8 * cm)
137                width = float(width) / multi
138                height = float(height) / multi
139                self.ypos -= height
140                c.drawImage(logo, xcenter - (width / 2.0), \
141                                  self.ypos, \
142                                  width, height)
143
144        self.ypos -= (cm + 20)
145        self.canvas.setFont("Helvetica-Bold", 14)
146        self.canvas.setFillColorRGB(0, 0, 0)
147        msg = _("Here's the invoice for your printouts")
148        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
149
150        self.yorigine = self.ypos
151        self.canvas.restoreState()
152        self.canvas.endForm()
153
154    def endPDF(self, fname) :
155        """Flushes the PDF generator."""
156        self.canvas.save()
157        if fname != "-" :
158            outfile = open(fname, "w")
159            outfile.write(self.pdfDocument.getvalue())
160            outfile.close()
161        else :
162            sys.stdout.write(self.pdfDocument.getvalue())
163            sys.stdout.flush()
164
165    def genInvoices(self, peruser, logo, outfname, firstnumber, unit, vat) :
166        """Generates the invoices file."""
167        if len(peruser) :
168            percent = Percent(self, size=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        self.adminOnly()
193
194        self.pagesize = getPageSize(options.pagesize)
195
196        extractonly = {}
197        for filterexp in arguments :
198            if filterexp.strip() :
199                try :
200                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
201                    filterkey = filterkey.lower()
202                    if filterkey not in self.validfilterkeys :
203                        raise ValueError
204                except ValueError :
205                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
206                else :
207                    extractonly.update({ filterkey : filtervalue })
208
209        percent = Percent(self)
210        outfname = options.output.strip().encode(sys.getfilesystemencoding())
211        if outfname != "-" :
212            percent.display("%s..." % _("Extracting datas"))
213
214        username = extractonly.get("username")
215        if username :
216            user = self.storage.getUser(username)
217        else :
218            user = None
219
220        printername = extractonly.get("printername")
221        if printername :
222            printer = self.storage.getPrinter(printername)
223        else :
224            printer = None
225
226        start = extractonly.get("start")
227        end = extractonly.get("end")
228        (start, end) = self.storage.cleanDates(start, end)
229
230        jobs = self.storage.retrieveHistory(user=user,
231                                            printer=printer,
232                                            hostname=extractonly.get("hostname"),
233                                            billingcode=extractonly.get("billingcode"),
234                                            jobid=extractonly.get("jobid"),
235                                            start=start,
236                                            end=end,
237                                            limit=0)
238
239        peruser = {}
240        nbjobs = 0
241        nbpages = 0
242        nbcredits = 0.0
243        percent.setSize(len(jobs))
244        if outfname != "-" :
245            percent.display("\n")
246        for job in jobs :
247            if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
248                nbpages += job.JobSize
249                nbcredits += job.JobPrice
250                counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
251                counters["nbpages"] += job.JobSize
252                counters["nbcredits"] += job.JobPrice
253                counters["nbjobs"] += 1
254                nbjobs += 1
255                if outfname != "-" :
256                    percent.oneMore()
257        if outfname != "-" :
258            percent.done()
259        self.genInvoices(peruser,
260                         options.logo.strip().encode(sys.getfilesystemencoding()),
261                         outfname,
262                         options.number,
263                         options.unit or _("Credits"),
264                         options.vat)
265        if outfname != "-" :
266            nbusers = len(peruser)
267            self.display("%s\n" % (_("Invoiced %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
268                     % locals()))
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=_("Set 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.