root / pykota / trunk / bin / pkrefund @ 3413

Revision 3413, 15.4 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
RevLine 
[3063]1#! /usr/bin/env python
[3411]2# -*- coding: utf-8 -*-*-
[3063]3#
[3260]4# PyKota : Print Quotas for CUPS
[3063]5#
[3275]6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
[3260]7# This program is free software: you can redistribute it and/or modify
[3063]8# it under the terms of the GNU General Public License as published by
[3260]9# the Free Software Foundation, either version 3 of the License, or
[3063]10# (at your option) any later version.
[3413]11#
[3063]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.
[3413]16#
[3063]17# You should have received a copy of the GNU General Public License
[3260]18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
[3063]19#
20# $Id$
21#
22#
23
[3347]24"""A refunding tool for PyKota"""
25
[3063]26import sys
[3084]27import os
28import pwd
29import time
30import cStringIO
[3063]31
[3084]32try :
33    from reportlab.pdfgen import canvas
34    from reportlab.lib import pagesizes
35    from reportlab.lib.units import cm
[3413]36except ImportError :
[3288]37    hasRL = False
[3413]38else :
[3288]39    hasRL = True
[3413]40
[3084]41try :
[3413]42    import PIL.Image
43except ImportError :
[3288]44    hasPIL = False
[3413]45else :
[3288]46    hasPIL = True
[3063]47
[3294]48import pykota.appinit
[3347]49from pykota.utils import run
50from pykota.commandline import PyKotaOptionParser, \
51                               checkandset_pagesize, \
52                               checkandset_positiveint
53from pykota.pdfutils import getPageSize
[3288]54from pykota.errors import PyKotaToolError, PyKotaCommandLineError
[3295]55from pykota.tool import Percent, PyKotaTool
[3413]56
57class PKRefund(PyKotaTool) :
[3063]58    """A class for refund manager."""
59    validfilterkeys = [ "username",
60                        "printername",
61                        "hostname",
62                        "jobid",
63                        "billingcode",
64                        "start",
65                        "end",
66                      ]
[3413]67
[3084]68    def printVar(self, label, value, size) :
69        """Outputs a variable onto the PDF canvas.
[3413]70
[3084]71           Returns the number of points to substract to current Y coordinate.
[3413]72        """
[3084]73        xcenter = (self.pagesize[0] / 2.0) - 1*cm
74        self.canvas.saveState()
75        self.canvas.setFont("Helvetica-Bold", size)
76        self.canvas.setFillColorRGB(0, 0, 0)
[3303]77        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label)
[3084]78        self.canvas.setFont("Courier-Bold", size)
79        self.canvas.setFillColorRGB(0, 0, 1)
[3303]80        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value)
[3084]81        self.canvas.restoreState()
82        self.ypos -= (size + 4)
[3413]83
[3088]84    def pagePDF(self, receiptnumber, name, values, unit, reason) :
[3084]85        """Generates a new page in the PDF document."""
86        if values["nbpages"] :
87            self.canvas.doForm("background")
88            self.ypos = self.yorigine - (cm + 20)
89            self.printVar(_("Refunding receipt"), "#%s" % receiptnumber, 22)
90            self.printVar(_("Username"), name, 22)
91            self.ypos -= 20
[3347]92            datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
93            self.printVar(_("Edited on"), datetime, 14)
[3413]94
[3084]95            self.ypos -= 20
[3089]96            self.printVar(_("Jobs refunded"), str(values["nbjobs"]), 18)
97            self.printVar(_("Pages refunded"), str(values["nbpages"]), 18)
98            self.printVar(_("Amount refunded"), "%.3f %s" % (values["nbcredits"], unit), 18)
[3084]99            self.ypos -= 20
100            self.printVar(_("Reason"), reason, 14)
101            self.canvas.showPage()
102            return 1
[3413]103        return 0
104
[3084]105    def initPDF(self, logo) :
106        """Initializes the PDF document."""
[3413]107        self.pdfDocument = cStringIO.StringIO()
[3084]108        self.canvas = c = canvas.Canvas(self.pdfDocument, \
109                                        pagesize=self.pagesize, \
110                                        pageCompression=1)
[3413]111
[3276]112        c.setAuthor(self.effectiveUserName)
[3303]113        c.setTitle(_("PyKota print job refunding receipts"))
114        c.setSubject(_("Print job refunding receipts generated with PyKota"))
[3413]115
[3084]116        self.canvas.beginForm("background")
117        self.canvas.saveState()
[3413]118
119        self.ypos = self.pagesize[1] - (2 * cm)
120
[3084]121        xcenter = self.pagesize[0] / 2.0
122        if logo :
[3413]123            try :
[3084]124                imglogo = PIL.Image.open(logo)
[3413]125            except IOError :
[3084]126                self.printInfo("Unable to open image %s" % logo, "warn")
127            else :
128                (width, height) = imglogo.size
[3413]129                multi = float(width) / (8 * cm)
[3084]130                width = float(width) / multi
131                height = float(height) / multi
132                self.ypos -= height
133                c.drawImage(logo, xcenter - (width / 2.0), \
134                                  self.ypos, \
135                                  width, height)
[3413]136
[3084]137        self.ypos -= (cm + 20)
138        self.canvas.setFont("Helvetica-Bold", 14)
139        self.canvas.setFillColorRGB(0, 0, 0)
140        msg = _("Here's the receipt for the refunding of your print jobs")
[3303]141        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
[3413]142
[3084]143        self.yorigine = self.ypos
144        self.canvas.restoreState()
145        self.canvas.endForm()
[3413]146
147    def endPDF(self, fname) :
[3084]148        """Flushes the PDF generator."""
149        self.canvas.save()
[3413]150        if fname != "-" :
[3084]151            outfile = open(fname, "w")
152            outfile.write(self.pdfDocument.getvalue())
153            outfile.close()
[3413]154        else :
[3084]155            sys.stdout.write(self.pdfDocument.getvalue())
156            sys.stdout.flush()
[3413]157
[3088]158    def genReceipts(self, peruser, logo, outfname, firstnumber, reason, unit) :
[3084]159        """Generates the receipts file."""
[3347]160        if len(peruser) :
[3088]161            percent = Percent(self, size=len(peruser))
[3084]162            if outfname != "-" :
163                percent.display("%s...\n" % _("Generating receipts"))
[3413]164
[3084]165            self.initPDF(logo)
166            number = firstnumber
167            for (name, values) in peruser.items() :
[3088]168                number += self.pagePDF(number, name, values, unit, reason)
[3084]169                if outfname != "-" :
170                    percent.oneMore()
[3413]171
[3084]172            if number > firstnumber :
173                self.endPDF(outfname)
[3413]174
[3084]175            if outfname != "-" :
176                percent.done()
[3413]177
[3347]178    def main(self, arguments, options) :
179        """Refunds jobs."""
[3084]180        if not hasRL :
181            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
182        if not hasPIL :
183            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
[3413]184
185        self.adminOnly()
186
[3347]187        self.pagesize = getPageSize(options.pagesize)
[3413]188
[3347]189        if (not options.reason) or (not options.reason.strip()) :
[3084]190            raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.")
[3413]191
[3063]192        extractonly = {}
193        for filterexp in arguments :
194            if filterexp.strip() :
195                try :
196                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
197                    filterkey = filterkey.lower()
198                    if filterkey not in self.validfilterkeys :
[3413]199                        raise ValueError
200                except ValueError :
[3063]201                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
[3413]202                else :
[3063]203                    extractonly.update({ filterkey : filtervalue })
[3413]204
[3095]205        percent = Percent(self)
[3347]206        outfname = options.output.strip().encode(sys.getfilesystemencoding())
[3095]207        if outfname != "-" :
208            percent.display("%s..." % _("Extracting datas"))
[3413]209        else :
[3347]210            options.force = True
211            self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn")
[3413]212
213        username = extractonly.get("username")
[3063]214        if username :
215            user = self.storage.getUser(username)
216        else :
217            user = None
[3413]218
219        printername = extractonly.get("printername")
[3063]220        if printername :
221            printer = self.storage.getPrinter(printername)
[3413]222        else :
[3063]223            printer = None
[3413]224
[3063]225        start = extractonly.get("start")
226        end = extractonly.get("end")
227        (start, end) = self.storage.cleanDates(start, end)
[3413]228
229        jobs = self.storage.retrieveHistory(user=user,
230                                            printer=printer,
[3063]231                                            hostname=extractonly.get("hostname"),
232                                            billingcode=extractonly.get("billingcode"),
233                                            jobid=extractonly.get("jobid"),
234                                            start=start,
235                                            end=end,
236                                            limit=0)
[3413]237
238        peruser = {}
239        nbjobs = 0
240        nbpages = 0
[3063]241        nbcredits = 0.0
[3095]242        percent.setSize(len(jobs))
243        if outfname != "-" :
244            percent.display("\n")
[3413]245        for job in jobs :
[3063]246            if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
[3347]247                if options.force :
[3063]248                    nbpages += job.JobSize
249                    nbcredits += job.JobPrice
[3066]250                    counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
251                    counters["nbpages"] += job.JobSize
252                    counters["nbcredits"] += job.JobPrice
[3347]253                    job.refund(options.reason)
[3066]254                    counters["nbjobs"] += 1
[3063]255                    nbjobs += 1
[3095]256                    if outfname != "-" :
257                        percent.oneMore()
[3413]258                else :
[3063]259                    print _("Date : %s") % str(job.JobDate)[:19]
[3109]260                    print _("Printer : %s") % job.PrinterName
261                    print _("User : %s") % job.UserName
[3063]262                    print _("JobId : %s") % job.JobId
[3109]263                    print _("Title : %s") % job.JobTitle
264                    if job.JobBillingCode :
265                        print _("Billing code : %s") % job.JobBillingCode
[3063]266                    print _("Pages : %i") % job.JobSize
267                    print _("Credits : %.3f") % job.JobPrice
[3413]268
269                    while True :
[3063]270                        answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper()
271                        if answer == _("Y") :
272                            nbpages += job.JobSize
273                            nbcredits += job.JobPrice
[3066]274                            counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
275                            counters["nbpages"] += job.JobSize
276                            counters["nbcredits"] += job.JobPrice
[3347]277                            job.refund(options.reason)
[3066]278                            counters["nbjobs"] += 1
[3063]279                            nbjobs += 1
280                            break
[3413]281                        elif answer == _("N") :
282                            break
283                    print
[3095]284        if outfname != "-" :
285            percent.done()
[3413]286        self.genReceipts(peruser,
287                         options.logo.strip().encode(sys.getfilesystemencoding()),
288                         outfname,
289                         options.number,
290                         options.reason,
[3347]291                         options.unit or _("Credits"))
[3413]292        if outfname != "-" :
[3347]293            nbusers = len(peruser)
294            print _("Refunded %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
295                     % locals()
[3413]296
297if __name__ == "__main__" :
[3347]298    parser = PyKotaOptionParser(description=_("Refunding tool for PyKota."),
299                                usage="pkrefund [options] [filterexpr]")
300    parser.add_option("-f", "--force",
301                            dest="force",
302                            action="store_true",
303                            help=_("Doesn't ask for confirmation before refunding."))
304    parser.add_option("-l", "--logo",
305                            dest="logo",
306                            default=u"/usr/share/pykota/logos/pykota.jpeg",
[3360]307                            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."))
[3347]308    parser.add_option("-p", "--pagesize",
309                            type="string",
310                            action="callback",
311                            callback=checkandset_pagesize,
312                            dest="pagesize",
313                            default=u"A4",
[3360]314                            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."))
[3413]315    parser.add_option("-n", "--number",
[3347]316                            dest="number",
317                            type="int",
318                            action="callback",
319                            callback=checkandset_positiveint,
320                            default=1,
[3360]321                            help=_("Sets the number of the first receipt. This number will automatically be incremented for each receipt. The default value is %default."))
[3347]322    parser.add_option("-o", "--output",
323                            dest="output",
324                            type="string",
325                            default=u"-",
326                            help=_("The name of the file to which the PDF receipts will be written. If not set or set to '%default', the PDF document will be sent to the standard output."))
327    parser.add_option("-r", "--reason",
328                            dest="reason",
329                            type="string",
330                            help=_("The reason why there was a refund."))
[3413]331
[3347]332    # TODO : due to Python's optparse.py bug #1498146 fixed in rev 46861
333    # TODO : we can't use 'default=_("Credits")' for this option
[3413]334    parser.add_option("-u", "--unit",
[3347]335                            dest="unit",
336                            type="string",
337                            help=_("The name of the unit to use on the receipts. The default value is 'Credits' or its locale translation."))
[3413]338
[3347]339    parser.add_filterexpression("username", _("User's name"))
340    parser.add_filterexpression("printername", _("Printer's name"))
341    parser.add_filterexpression("hostname", _("Host's name"))
342    parser.add_filterexpression("jobid", _("Job's id"))
343    parser.add_filterexpression("billingcode", _("Job's billing code"))
344    parser.add_filterexpression("start", _("Job's date of printing"))
345    parser.add_filterexpression("end", _("Job's date of printing"))
[3413]346
[3347]347    parser.add_example('--output /tmp/receipts.pdf jobid=503',
348                       _("This would refund all jobs which Id is 503. A confirmation would be asked for each job to refund, and a PDF file named /tmp/receipts.pdf would be created containing printable receipts. BEWARE of job ids rolling over if you reset CUPS' history."))
[3413]349
[3347]350    parser.add_example('--reason "Hardware problem" jobid=503 start=today-7',
351                       _("This would refund all jobs which id is 503 but which would have been printed during the  past week. The reason would be marked as being an hardware problem."))
[3413]352
[3347]353    parser.add_example('--force username=jerome printername=HP2100',
354                       _("This would refund all jobs printed by user jerome on printer HP2100. No confirmation would be asked."))
[3413]355
[3347]356    parser.add_example('--force printername=HP2100 start=200602 end=yesterday',
357                       _("This would refund all jobs printed on printer HP2100 between February 1st 2006 and yesterday. No confirmation would be asked."))
[3413]358
[3347]359    (options, arguments) = parser.parse_args()
[3413]360    run(parser, PKRefund)
Note: See TracBrowser for help on using the browser.