root / pykota / trunk / bin / pkrefund @ 3411

Revision 3411, 16.2 kB (checked in by jerome, 16 years ago)

Minor change to please emacs...

  • 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        self.adminOnly()   
186           
187        self.pagesize = getPageSize(options.pagesize)
188       
189        if (not options.reason) or (not options.reason.strip()) :
190            raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.")
191           
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 :
199                        raise ValueError               
200                except ValueError :   
201                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
202                else :   
203                    extractonly.update({ filterkey : filtervalue })
204           
205        percent = Percent(self)
206        outfname = options.output.strip().encode(sys.getfilesystemencoding())
207        if outfname != "-" :
208            percent.display("%s..." % _("Extracting datas"))
209        else :   
210            options.force = True
211            self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn")
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                if options.force :
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                    job.refund(options.reason)
254                    counters["nbjobs"] += 1
255                    nbjobs += 1
256                    if outfname != "-" :
257                        percent.oneMore()
258                else :   
259                    print _("Date : %s") % str(job.JobDate)[:19]
260                    print _("Printer : %s") % job.PrinterName
261                    print _("User : %s") % job.UserName
262                    print _("JobId : %s") % job.JobId
263                    print _("Title : %s") % job.JobTitle
264                    if job.JobBillingCode :
265                        print _("Billing code : %s") % job.JobBillingCode
266                    print _("Pages : %i") % job.JobSize
267                    print _("Credits : %.3f") % job.JobPrice
268                   
269                    while True :                             
270                        answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper()
271                        if answer == _("Y") :
272                            nbpages += job.JobSize
273                            nbcredits += job.JobPrice
274                            counters = peruser.setdefault(job.UserName, { "nbjobs" : 0, "nbpages" : 0, "nbcredits" : 0.0 })
275                            counters["nbpages"] += job.JobSize
276                            counters["nbcredits"] += job.JobPrice
277                            job.refund(options.reason)
278                            counters["nbjobs"] += 1
279                            nbjobs += 1
280                            break   
281                        elif answer == _("N") :   
282                            break
283                    print       
284        if outfname != "-" :
285            percent.done()
286        self.genReceipts(peruser, 
287                         options.logo.strip().encode(sys.getfilesystemencoding()), 
288                         outfname, 
289                         options.number, 
290                         options.reason, 
291                         options.unit or _("Credits"))
292        if outfname != "-" :   
293            nbusers = len(peruser)
294            print _("Refunded %(nbusers)i users for %(nbjobs)i jobs, %(nbpages)i pages and %(nbcredits).3f credits") \
295                     % locals()
296           
297if __name__ == "__main__" : 
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",
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."))
308    parser.add_option("-p", "--pagesize",
309                            type="string",
310                            action="callback",
311                            callback=checkandset_pagesize,
312                            dest="pagesize",
313                            default=u"A4",
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."))
315    parser.add_option("-n", "--number",                       
316                            dest="number",
317                            type="int",
318                            action="callback",
319                            callback=checkandset_positiveint,
320                            default=1,
321                            help=_("Sets the number of the first receipt. This number will automatically be incremented for each receipt. The default value is %default."))
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."))
331                           
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
334    parser.add_option("-u", "--unit",                   
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."))
338                           
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"))
346   
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."))
349 
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."))
352 
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."))
355 
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."))
358   
359    (options, arguments) = parser.parse_args()
360    run(parser, PKRefund)                   
Note: See TracBrowser for help on using the browser.