Index: /pykota/trunk/bin/pkrefund
===================================================================
--- /pykota/trunk/bin/pkrefund (revision 3068)
+++ /pykota/trunk/bin/pkrefund (revision 3084)
@@ -1,4 +1,6 @@
 #! /usr/bin/env python
 # -*- coding: ISO-8859-15 -*-
+
+"""pkrefund is a tool to refund print jobs and generate PDF receipts."""
 
 # PyKota Print Job Refund Manager
@@ -26,6 +28,26 @@
 
 import sys
-
-from pykota.tool import PyKotaTool, PyKotaCommandLineError, crashed, N_
+import os
+import pwd
+import time
+import cStringIO
+
+try :
+    from reportlab.pdfgen import canvas
+    from reportlab.lib import pagesizes
+    from reportlab.lib.units import cm
+except ImportError :    
+    hasRL = 0
+else :    
+    hasRL = 1
+    
+try :
+    import PIL.Image 
+except ImportError :    
+    hasPIL = 0
+else :    
+    hasPIL = 1
+
+from pykota.tool import Percent, PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
 
 __doc__ = N_("""pkrefund v%(__version__)s (c) %(__years__)s %(__author__)s
@@ -44,4 +66,25 @@
   -f | --force         Doesn't ask for confirmation before refunding jobs.
   -r | --reason txt    Sets textual information to explain the refunding.
+
+  -l | --logo img      Use the image as the receipt's logo. The logo will
+                       be drawn at the center top of the page. The default
+                       logo is /usr/share/pykota/logos/pykota.jpeg
+
+  -p | --pagesize sz   Sets sz as the page size. Most well known
+                       page sizes are recognized, like 'A4' or 'Letter'
+                       to name a few. The default size is A4.
+
+  -n | --number N      Sets the number of the first receipt. This number
+                       will automatically be incremented for each receipt.
+
+  -o | --output f.pdf  Defines the name of the PDF file which will contain
+                       the receipts. If not set, then no PDF file will
+                       be created. If set to '-', then --force is assumed,
+                       and the PDF document is sent to standard output.
+
+  -u | --unit u        Defines the name of the unit to use on the receipts.
+                       The default unit is 'Credits', optionally translated
+                       to your native language if it is supported by PyKota.
+  
 
   Use the filter expressions to extract only parts of the 
@@ -83,9 +126,11 @@
 Examples :
 
-  $ pkrefund jobid=503
+  $ pkrefund --output /tmp/receipts.pdf jobid=503
   
   This will refund all jobs which Id is 503. BEWARE : installing CUPS
-  afresh will reset the first job id at 1. So you probably want to use
-  a more precise filter as explained below
+  afresh will reset the first job id at 1, so you probably want to use
+  a more precise filter as explained below. A confirmation will
+  be asked for each job to refund, and a PDF file named /tmp/receipts.pdf
+  will be created which will contain printable receipts.
   
   $ pkrefund --reason "Hardware problem" jobid=503 start=today-7
@@ -115,8 +160,156 @@
                         "end",
                       ]
+                      
+    def getPageSize(self, pgsize) :
+        """Returns the correct page size or None if not found."""
+        try :
+            return getattr(pagesizes, pgsize.upper())
+        except AttributeError :    
+            try :
+                return getattr(pagesizes, pgsize.lower())
+            except AttributeError :
+                pass
+                
+    def printVar(self, label, value, size) :
+        """Outputs a variable onto the PDF canvas.
+        
+           Returns the number of points to substract to current Y coordinate.
+        """   
+        xcenter = (self.pagesize[0] / 2.0) - 1*cm
+        self.canvas.saveState()
+        self.canvas.setFont("Helvetica-Bold", size)
+        self.canvas.setFillColorRGB(0, 0, 0)
+        self.canvas.drawRightString(xcenter, self.ypos, "%s :" % self.userCharsetToUTF8(label))
+        self.canvas.setFont("Courier-Bold", size)
+        self.canvas.setFillColorRGB(0, 0, 1)
+        self.canvas.drawString(xcenter + 0.5*cm, self.ypos, self.userCharsetToUTF8(value))
+        self.canvas.restoreState()
+        self.ypos -= (size + 4)
+        
+    def pagePDF(self, receiptnumber, name, values, unitname, reason) :
+        """Generates a new page in the PDF document."""
+        if values["nbpages"] :
+            self.canvas.doForm("background")
+            self.ypos = self.yorigine - (cm + 20)
+            self.printVar(_("Refunding receipt"), "#%s" % receiptnumber, 22)
+            self.printVar(_("Username"), name, 22)
+            self.ypos -= 20
+            self.printVar(_("Edited on"), time.strftime("%c", time.localtime()), 14)
+                
+            self.ypos -= 20
+            self.printVar(_("Jobs refunded"), str(values["nbjobs"]), 22)
+            self.printVar(_("Pages refunded"), str(values["nbpages"]), 22)
+            self.printVar(_("Amount refunded"), "%.3f %s" % (values["nbcredits"], unitname), 22)
+            self.ypos -= 20
+            self.printVar(_("Reason"), reason, 14)
+            self.canvas.showPage()
+            return 1
+        return 0    
+        
+    def initPDF(self, logo) :
+        """Initializes the PDF document."""
+        self.pdfDocument = cStringIO.StringIO()        
+        self.canvas = c = canvas.Canvas(self.pdfDocument, \
+                                        pagesize=self.pagesize, \
+                                        pageCompression=1)
+        
+        c.setAuthor(self.originalUserName)
+        c.setTitle("PyKota print job refunding receipts")
+        c.setSubject("Print job refunding receipts generated with PyKota")
+        
+        
+        self.canvas.beginForm("background")
+        self.canvas.saveState()
+        
+        self.ypos = self.pagesize[1] - (2 * cm)            
+        
+        xcenter = self.pagesize[0] / 2.0
+        if logo :
+            try :    
+                imglogo = PIL.Image.open(logo)
+            except IOError :    
+                self.printInfo("Unable to open image %s" % logo, "warn")
+            else :
+                (width, height) = imglogo.size
+                multi = float(width) / (8 * cm) 
+                width = float(width) / multi
+                height = float(height) / multi
+                self.ypos -= height
+                c.drawImage(logo, xcenter - (width / 2.0), \
+                                  self.ypos, \
+                                  width, height)
+        
+        self.ypos -= (cm + 20)
+        self.canvas.setFont("Helvetica-Bold", 14)
+        self.canvas.setFillColorRGB(0, 0, 0)
+        msg = _("Here's the receipt for the refunding of your print jobs")
+        self.canvas.drawCentredString(xcenter, self.ypos, "%s :" % self.userCharsetToUTF8(msg))
+        
+        self.yorigine = self.ypos
+        self.canvas.restoreState()
+        self.canvas.endForm()
+        
+    def endPDF(self, fname) :    
+        """Flushes the PDF generator."""
+        self.canvas.save()
+        if fname != "-" :        
+            outfile = open(fname, "w")
+            outfile.write(self.pdfDocument.getvalue())
+            outfile.close()
+        else :    
+            sys.stdout.write(self.pdfDocument.getvalue())
+            sys.stdout.flush()
+        
+    def genReceipts(self, peruser, logo, outfname, firstnumber, reason) :
+        """Generates the receipts file."""
+        if outfname and len(peruser) :
+            percent = Percent(self)
+            percent.setSize(len(peruser))
+            if outfname != "-" :
+                percent.display("%s...\n" % _("Generating receipts"))
+                
+            self.initPDF(logo)
+            number = firstnumber
+            for (name, values) in peruser.items() :
+                number += self.pagePDF(number, name, values, options["unit"], reason)
+                if outfname != "-" :
+                    percent.oneMore()
+                    
+            if number > firstnumber :
+                self.endPDF(outfname)
+                
+            if outfname != "-" :
+                percent.done()
+        
     def main(self, arguments, options, restricted=1) :
         """Print Quota Data Dumper."""
+        if not hasRL :
+            raise PyKotaToolError, "The ReportLab module is missing. Download it from http://www.reportlab.org"
+        if not hasPIL :
+            raise PyKotaToolError, "The Python Imaging Library is missing. Download it from http://www.pythonware.com/downloads"
+            
         if restricted and not self.config.isAdmin :
             raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
+            
+        if (not options["reason"]) or not options["reason"].strip() :
+            raise PyKotaCommandLineError, _("Refunding for no reason is forbidden. Please use the --reason command line option.")
+            
+        if options["output"] :
+            options["output"] = options["output"].strip()
+            if options["output"] == "-" :
+                options["force"] = True
+                self.printInfo(_("The PDF file containing the receipts will be sent to stdout. --force is assumed."), "warn")
+            
+        try :    
+            firstnumber = int(options["number"])
+            if firstnumber <= 0 :
+                raise ValueError
+        except (ValueError, TypeError) :    
+            raise PyKotaCommandLineError, _("Incorrect value '%s' for the --number command line option") % options["number"]
+            
+        self.pagesize = self.getPageSize(options["pagesize"])
+        if self.pagesize is None :
+            self.pagesize = self.getPageSize("a4")
+            self.printInfo(_("Invalid 'pagesize' option %s, defaulting to A4.") % options["pagesize"], "warn")
             
         extractonly = {}
@@ -198,11 +391,18 @@
                             break
                     print        
-        print _("Refunded %i jobs, %i pages and %.3f credits") % (nbjobs, nbpages, nbcredits)                                 
+        self.genReceipts(peruser, options["logo"].strip(), options["output"], firstnumber, reason)
+        if options["output"] != "-" :    
+            print _("Refunded %i jobs, %i pages and %.3f credits") % (nbjobs, nbpages, nbcredits)
             
 if __name__ == "__main__" : 
     retcode = 0
     try :
-        short_options = "vhfr"
-        long_options = ["help", "version", "force", "reason="]
+        defaults = { "unit" : N_("Credits"),
+                     "pagesize" : "a4", \
+                     "logo" : "/usr/share/pykota/logos/pykota.jpeg",
+                     "number" : "1",
+                   }
+        short_options = "vhfru:o:p:l:n:"
+        long_options = ["help", "version", "force", "reason=", "unit=", "output=", "pagesize=", "logo=", "number="]
         
         # Initializes the command line tool
@@ -218,4 +418,11 @@
         options["force"] = options["f"] or options["force"]
         options["reason"] = options["r"] or options["reason"]
+        options["unit"] = options["u"] or options["unit"] or defaults["unit"]
+        options["output"] = options["o"] or options["output"]
+        options["pagesize"] = options["p"] or options["pagesize"] or defaults["pagesize"]
+        options["number"] = options["n"] or options["number"] or defaults["number"]
+        options["logo"] = options["l"] or options["logo"]
+        if options["logo"] is None : # Allows --logo="" to disable the logo entirely
+            options["logo"] = defaults["logo"]  
         
         if options["help"] :
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 3079)
+++ /pykota/trunk/NEWS (revision 3084)
@@ -24,4 +24,6 @@
     - 1.25alpha17 (2006-11-27) :
     
+        - pkrefund can now create receipts in the PDF format.
+        
         - The code was changed to accomodate ReportLab 2.x in pkbanner
           and pkinvoice. ReportLab v1.x is now only supported with 
