root / pykota / trunk / bin / pkrefund @ 3347

Revision 3347, 16.3 kB (checked in by jerome, 16 years ago)

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"""A refunding tool 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
53from pykota.pdfutils import getPageSize
54from pykota.errors import PyKotaToolError, PyKotaCommandLineError
55from pykota.tool import Percent, PyKotaTool
56       
57class PKRefund(PyKotaTool) :       
58    """A class for refund manager."""
59    validfilterkeys = [ "username",
60                        "printername",
61                        "hostname",
62                        "jobid",
63                        "billingcode",
64                        "start",
65                        "end",
66                      ]
67                     
68    def printVar(self, label, value, size) :
69        """Outputs a variable onto the PDF canvas.
70       
71           Returns the number of points to substract to current Y coordinate.
72        """   
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)
77        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % label)
78        self.canvas.setFont("Courier-Bold", size)
79        self.canvas.setFillColorRGB(0, 0, 1)
80        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, value)
81        self.canvas.restoreState()
82        self.ypos -= (size + 4)
83       
84    def pagePDF(self, receiptnumber, name, values, unit, reason) :
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
92            datetime = time.strftime("%c", time.localtime()).decode(self.charset, "replace")
93            self.printVar(_("Edited on"), datetime, 14)
94               
95            self.ypos -= 20
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)
99            self.ypos -= 20
100            self.printVar(_("Reason"), reason, 14)
101            self.canvas.showPage()
102            return 1
103        return 0   
104       
105    def initPDF(self, logo) :
106        """Initializes the PDF document."""
107        self.pdfDocument = cStringIO.StringIO()       
108        self.canvas = c = canvas.Canvas(self.pdfDocument, \
109                                        pagesize=self.pagesize, \
110                                        pageCompression=1)
111       
112        c.setAuthor(self.effectiveUserName)
113        c.setTitle(_("PyKota print job refunding receipts"))
114        c.setSubject(_("Print job refunding receipts generated with PyKota"))
115       
116        self.canvas.beginForm("background")
117        self.canvas.saveState()
118       
119        self.ypos = self.pagesize[1] - (2 * cm)           
120       
121        xcenter = self.pagesize[0] / 2.0
122        if logo :
123            try :   
124                imglogo = PIL.Image.open(logo)
125            except IOError :   
126                self.printInfo("Unable to open image %s" % logo, "warn")
127            else :
128                (width, height) = imglogo.size
129                multi = float(width) / (8 * cm) 
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)
136       
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")
141        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % msg)
142       
143        self.yorigine = self.ypos
144        self.canvas.restoreState()
145        self.canvas.endForm()
146       
147    def endPDF(self, fname) :   
148        """Flushes the PDF generator."""
149        self.canvas.save()
150        if fname != "-" :       
151            outfile = open(fname, "w")
152            outfile.write(self.pdfDocument.getvalue())
153            outfile.close()
154        else :   
155            sys.stdout.write(self.pdfDocument.getvalue())
156            sys.stdout.flush()
157       
158    def genReceipts(self, peruser, logo, outfname, firstnumber, reason, unit) :
159        """Generates the receipts file."""
160        if len(peruser) :
161            percent = Percent(self, size=len(peruser))
162            if outfname != "-" :
163                percent.display("%s...\n" % _("Generating receipts"))
164               
165            self.initPDF(logo)
166            number = firstnumber
167            for (name, values) in peruser.items() :
168                number += self.pagePDF(number, name, values, unit, reason)
169                if outfname != "-" :
170                    percent.oneMore()
171                   
172            if number > firstnumber :
173                self.endPDF(outfname)
174               
175            if outfname != "-" :
176                percent.done()
177       
178    def main(self, arguments, options) :
179        """Refunds jobs."""
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"
184           
185        if not self.config.isAdmin :
186            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
187           
188        self.pagesize = getPageSize(options.pagesize)
189       
190        if (not options.reason) or (not options.reason.strip()) :
191            raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.")
192           
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 :
200                        raise ValueError               
201                except ValueError :   
202                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
203                else :   
204                    extractonly.update({ filterkey : filtervalue })
205           
206        percent = Percent(self)
207        outfname = options.output.strip().encode(sys.getfilesystemencoding())
208        if outfname != "-" :
209            percent.display("%s..." % _("Extracting datas"))
210        else :   
211            options.force = True
212            self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn")
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                if options.force :
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                    job.refund(options.reason)
255                    counters["nbjobs"] += 1
256                    nbjobs += 1
257                    if outfname != "-" :
258                        percent.oneMore()
259                else :   
260                    print _("Date : %s") % str(job.JobDate)[:19]
261                    print _("Printer : %s") % job.PrinterName
262                    print _("User : %s") % job.UserName
263                    print _("JobId : %s") % job.JobId
264                    print _("Title : %s") % job.JobTitle
265                    if job.JobBillingCode :
266                        print _("Billing code : %s") % job.JobBillingCode
267                    print _("Pages : %i") % job.JobSize
268                    print _("Credits : %.3f") % job.JobPrice
269                   
270                    while True :                             
271                        answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper()
272                        if answer == _("Y") :
273                            nbpages += job.JobSize
274                            nbcredits += job.JobPrice
275                            counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
276                            counters["nbpages"] += job.JobSize
277                            counters["nbcredits"] += job.JobPrice
278                            job.refund(options.reason)
279                            counters["nbjobs"] += 1
280                            nbjobs += 1
281                            break   
282                        elif answer == _("N") :   
283                            break
284                    print       
285        if outfname != "-" :
286            percent.done()
287        self.genReceipts(peruser, 
288                         options.logo.strip().encode(sys.getfilesystemencoding()), 
289                         outfname, 
290                         options.number, 
291                         options.reason, 
292                         options.unit or _("Credits"))
293        if outfname != "-" :   
294            nbusers = len(peruser)
295            print _("Refunded %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
296                     % locals()
297           
298if __name__ == "__main__" : 
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",
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"))
309    parser.add_option("-p", "--pagesize",
310                            type="string",
311                            action="callback",
312                            callback=checkandset_pagesize,
313                            dest="pagesize",
314                            default=u"A4",
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"))
316    parser.add_option("-n", "--number",                       
317                            dest="number",
318                            type="int",
319                            action="callback",
320                            callback=checkandset_positiveint,
321                            default=1,
322                            help=_("Sets the number of the first receipt. This number will automatically be incremented for each receipt. The default value is %default"))
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."))
332                           
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
335    parser.add_option("-u", "--unit",                   
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."))
339                           
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"))
347   
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."))
350 
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."))
353 
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."))
356 
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."))
359   
360    (options, arguments) = parser.parse_args()
361    run(parser, PKRefund)                   
Note: See TracBrowser for help on using the browser.