Index: /pykota/trunk/setup.py
===================================================================
--- /pykota/trunk/setup.py (revision 3033)
+++ /pykota/trunk/setup.py (revision 3063)
@@ -128,5 +128,5 @@
       packages = [ "pykota", "pykota.storages", "pykota.loggers", "pykota.accounters", "pykota.reporters" ],
       scripts = [ "bin/pknotify", "bin/pkusers", "bin/pkinvoice", "bin/pksetup", \
-                  "bin/pkturnkey", "bin/pkbcodes", "bin/pkmail", \
+                  "bin/pkrefund", "bin/pkturnkey", "bin/pkbcodes", "bin/pkmail", \
                   "bin/pkbanner", "bin/autopykota", "bin/dumpykota", \
                   "bin/pykosd", "bin/edpykota", "bin/repykota", \
Index: /pykota/trunk/bin/pkrefund
===================================================================
--- /pykota/trunk/bin/pkrefund (revision 3063)
+++ /pykota/trunk/bin/pkrefund (revision 3063)
@@ -0,0 +1,221 @@
+#! /usr/bin/env python
+# -*- coding: ISO-8859-15 -*-
+
+# PyKota Print Job Refund Manager
+#
+# PyKota - Print Quotas for CUPS and LPRng
+#
+# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# $Id$
+#
+#
+
+import sys
+
+from pykota.tool import PyKotaTool, PyKotaCommandLineError, crashed, N_
+
+__doc__ = N_("""pkrefund v%(__version__)s (c) %(__years__)s %(__author__)s
+
+Refunds jobs.
+
+command line usage :
+
+  pkrefund [options] [filterexpr]
+
+options :
+
+  -v | --version       Prints pkrefund's version number then exits.
+  -h | --help          Prints this message then exits.
+  
+  -f | --force         Doesn't ask for confirmation before refunding jobs.
+  -r | --reason txt    Sets textual information to explain the refunding.
+
+  Use the filter expressions to extract only parts of the 
+  datas. Allowed filters are of the form :
+                
+         key=value
+                         
+  Allowed keys for now are :  
+                       
+         username       User's name
+         printername    Printer's name
+         hostname       Client's hostname
+         jobid          Job's Id
+         billingcode    Job's billing code
+         start          Job's date of printing
+         end            Job's date of printing
+         
+  Contrary to other PyKota management tools, wildcard characters are not 
+  expanded, so you can't use them.
+  
+Examples :
+
+  $ pkrefund 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
+  
+  $ pkrefund --reason "Hardware problem" jobid=503 start=today-7
+  
+  Refunds all jobs which id is 503 but which were printed during the
+  past week. The reason will be marked as being an hardware problem.
+  
+  $ pkrefund --force username=jerome printername=HP2100
+  
+  Refunds all jobs printed by user jerome on printer HP2100. No
+  confirmation will be asked.
+  
+  $ pkrefund --force printername=HP2100 start=200602 end=yesterday
+  
+  Refunds all jobs printed on printer HP2100 between February 1st 2006
+  and yesterday. No confirmation will be asked.
+""")
+        
+class PkRefund(PyKotaTool) :        
+    """A class for refund manager."""
+    validfilterkeys = [ "username",
+                        "printername",
+                        "hostname",
+                        "jobid",
+                        "billingcode",
+                        "start",
+                        "end",
+                      ]
+    def main(self, arguments, options, restricted=1) :
+        """Print Quota Data Dumper."""
+        if restricted and not self.config.isAdmin :
+            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
+            
+        extractonly = {}
+        for filterexp in arguments :
+            if filterexp.strip() :
+                try :
+                    (filterkey, filtervalue) = [part.strip() for part in filterexp.split("=")]
+                    filterkey = filterkey.lower()
+                    if filterkey not in self.validfilterkeys :
+                        raise ValueError                
+                except ValueError :    
+                    raise PyKotaCommandLineError, _("Invalid filter value [%s], see help.") % filterexp
+                else :    
+                    extractonly.update({ filterkey : filtervalue })
+            
+        username = extractonly.get("username")    
+        if username :
+            user = self.storage.getUser(username)
+        else :
+            user = None
+            
+        printername = extractonly.get("printername")    
+        if printername :
+            printer = self.storage.getPrinter(printername)
+        else :    
+            printer = None
+            
+        start = extractonly.get("start")
+        end = extractonly.get("end")
+        (start, end) = self.storage.cleanDates(start, end)
+        
+        jobs = self.storage.retrieveHistory(user=user,    
+                                            printer=printer, 
+                                            hostname=extractonly.get("hostname"),
+                                            billingcode=extractonly.get("billingcode"),
+                                            jobid=extractonly.get("jobid"),
+                                            start=start,
+                                            end=end,
+                                            limit=0)
+        nbjobs = 0                                    
+        nbpages = 0                                            
+        nbcredits = 0.0
+        reason = (options.get("reason") or "").strip()
+        for job in jobs :                                    
+            if job.JobSize and (job.JobAction not in ("DENY", "CANCEL", "REFUND")) :
+                if options["force"] :
+                    nbpages += job.JobSize
+                    nbcredits += job.JobPrice
+                    job.refund(reason)
+                    nbjobs += 1
+                else :    
+                    print _("Date : %s") % str(job.JobDate)[:19]
+                    print _("JobId : %s") % job.JobId
+                    print _("Username : %s") % job.User.Name
+                    print _("Printername : %s") % job.Printer.Name
+                    print _("Billing code : %s") % job.JobBillingCode
+                    print _("Pages : %i") % job.JobSize
+                    print _("Credits : %.3f") % job.JobPrice
+                    print _("Title : %s") % job.JobTitle
+                    
+                    while True :                             
+                        answer = raw_input("\t%s ? " % _("Refund (Y/N)")).strip().upper()
+                        if answer == _("Y") :
+                            nbpages += job.JobSize
+                            nbcredits += job.JobPrice
+                            job.refund(reason)
+                            nbjobs += 1
+                            break    
+                        elif answer == _("N") :    
+                            break
+                    print        
+        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="]
+        
+        # Initializes the command line tool
+        refundmanager = PkRefund(doc=__doc__)
+        refundmanager.deferredInit()
+        
+        # parse and checks the command line
+        (options, args) = refundmanager.parseCommandline(sys.argv[1:], short_options, long_options, allownothing=1)
+        
+        # sets long options
+        options["help"] = options["h"] or options["help"]
+        options["version"] = options["v"] or options["version"]
+        options["force"] = options["f"] or options["force"]
+        options["reason"] = options["r"] or options["reason"]
+        
+        if options["help"] :
+            refundmanager.display_usage_and_quit()
+        elif options["version"] :
+            refundmanager.display_version_and_quit()
+        else :
+            retcode = refundmanager.main(args, options)
+    except KeyboardInterrupt :        
+        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
+        retcode = -3
+    except PyKotaCommandLineError, msg :    
+        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
+        retcode = -2
+    except SystemExit :        
+        pass
+    except :
+        try :
+            refundmanager.crashed("pkrefund failed")
+        except :    
+            crashed("pkrefund failed")
+        retcode = -1
+
+    try :
+        refundmanager.storage.close()
+    except (TypeError, NameError, AttributeError) :    
+        pass
+        
+    sys.exit(retcode)    
Index: /pykota/trunk/MANIFEST.in
===================================================================
--- /pykota/trunk/MANIFEST.in (revision 3033)
+++ /pykota/trunk/MANIFEST.in (revision 3063)
@@ -3,5 +3,5 @@
 include bin/autopykota bin/dumpykota bin/cupspykota bin/edpykota bin/warnpykota
 include bin/repykota bin/pykotme bin/pykosd bin/pkprinters bin/pkbcodes bin/pkinvoice
-include bin/pknotify bin/pkusers bin/pksetup
+include bin/pknotify bin/pkusers bin/pksetup bin/pkrefund
 include bin/waitprinter.sh bin/papwaitprinter.sh bin/mailandpopup.sh bin/README
 recursive-include po README *.sh *.po *.mo *.pot
