root / pykota / trunk / bin / pkrefund @ 3434

Revision 3434, 15.6 kB (checked in by jerome, 16 years ago)

Moved the progress report code to its own module.

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