Index: pykota/trunk/pykota/reporters/html.py
===================================================================
--- pykota/trunk/pykota/reporters/html.py (revision 1278)
+++ pykota/trunk/pykota/reporters/html.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.9  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.8  2004/01/12 15:28:45  jalet
 # Now can output the user's history on several printers at the same time.
@@ -81,5 +88,5 @@
                 else :    
                     oddevenclass = "even"
-                (pages, money, name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid) = self.getQuota(entry, entrypquota)
+                (pages, money, name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid, overcharge, warncount) = self.getQuota(entry, entrypquota)
                 if datelimit :
                     if datelimit == "DENY" :
@@ -89,5 +96,5 @@
                 if (not self.tool.config.getDisableHistory()) and (not self.isgroup) :
                     name = '<a href="%s?username=%s&printername=%s&history=1">%s</a>' % (os.environ.get("SCRIPT_NAME", ""), name, printer.Name, name)
-                self.report.append('<tr class="%s">%s</tr>' % (oddevenclass, "".join(["<td>%s</td>" % h for h in (name, reached, pagecounter, soft, hard, balance, datelimit or "&nbsp;", lifepagecounter, lifetimepaid)])))
+                self.report.append('<tr class="%s">%s</tr>' % (oddevenclass, "".join(["<td>%s</td>" % h for h in (name, reached, overcharge, pagecounter, soft, hard, balance, datelimit or "&nbsp;", lifepagecounter, lifetimepaid, warncount)])))
                 total += pages
                 totalmoney += money
@@ -95,6 +102,6 @@
             if total or totalmoney :        
                 (tpage, tmoney) = self.getTotals(total, totalmoney)
-                self.report.append('<tr class="totals"><td colspan="7">&nbsp;</td><td align="right">%s</td><td align="right">%s</td></tr>' % (tpage, tmoney))
-            self.report.append('<tr class="realpagecounter"><td colspan="7">&nbsp;</td><td align="right">%s</td></tr>' % self.getPrinterRealPageCounter(printer))
+                self.report.append('<tr class="totals"><td colspan="8">&nbsp;</td><td align="right">%s</td><td align="right">%s</td><td>&nbsp;</td></tr>' % (tpage, tmoney))
+            self.report.append('<tr class="realpagecounter"><td colspan="8">&nbsp;</td><td align="right">%s</td><td>&nbsp;</td></tr>' % self.getPrinterRealPageCounter(printer))
             self.report.append('</table>')
         if self.isgroup :    
Index: pykota/trunk/pykota/reporters/text.py
===================================================================
--- pykota/trunk/pykota/reporters/text.py (revision 1257)
+++ pykota/trunk/pykota/reporters/text.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.10  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.9  2004/01/08 14:10:33  jalet
 # Copyright year changed.
@@ -78,6 +85,6 @@
             self.report.append('-' * len(header))
             for (entry, entrypquota) in getattr(self.tool.storage, "getPrinter%ssAndQuotas" % prefix)(printer, self.ugnames) :
-                (pages, money, name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid) = self.getQuota(entry, entrypquota)
-                self.report.append("%-9.9s %s %7i %7s %7s %10s %-10.10s %8i %10s" % (name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid))
+                (pages, money, name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid, overcharge, warncount) = self.getQuota(entry, entrypquota)
+                self.report.append("%-15.15s %s %5s %7i %7s %7s %10s %-10.10s %8i %10s %4s" % (name, reached, overcharge, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid, warncount))
                 total += pages
                 totalmoney += money
@@ -85,6 +92,6 @@
             if total or totalmoney :        
                 (tpage, tmoney) = self.getTotals(total, totalmoney)
-                self.report.append((" " * 50) + tpage + tmoney)
-            self.report.append((" " * 51) + self.getPrinterRealPageCounter(printer))
+                self.report.append((" " * 62) + tpage + tmoney)
+            self.report.append((" " * 63) + self.getPrinterRealPageCounter(printer))
             self.report.append("")        
         if self.isgroup :    
Index: pykota/trunk/pykota/storages/sql.py
===================================================================
--- pykota/trunk/pykota/storages/sql.py (revision 2030)
+++ pykota/trunk/pykota/storages/sql.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.65  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.64  2005/01/18 19:47:50  jalet
 # Big bug fix wrt the datelimit attribute
@@ -273,4 +280,5 @@
             user.LifeTimePaid = fields.get("lifetimepaid")
             user.Email = fields.get("email")
+            user.OverCharge = fields.get("overcharge", 1.0)
             user.Exists = 1
         return user
@@ -317,4 +325,5 @@
                 userpquota.HardLimit = fields.get("hardlimit")
                 userpquota.DateLimit = fields.get("datelimit")
+                userpquota.WarnCount = fields.get("warncount")
                 userpquota.Exists = 1
         return userpquota
@@ -359,4 +368,7 @@
             lastjob.JobHostName = fields.get("hostname")
             lastjob.JobSizeBytes = fields.get("jobsizebytes")
+            lastjob.JobMD5Sum = fields.get("md5sum")
+            lastjob.JobPages = fields.get("pages")
+            lastjob.JobBillingCode = fields.get("billingcode")
             lastjob.Exists = 1
         return lastjob
@@ -374,4 +386,5 @@
                 user.LifeTimePaid = record.get("lifetimepaid")
                 user.Email = record.get("email")
+                user.OverCharge = record.get("overcharge")
                 user.Exists = 1
                 groupmembers.append(user)
@@ -423,5 +436,5 @@
         """Returns the list of users who uses a given printer, along with their quotas."""
         usersandquotas = []
-        result = self.doSearch("SELECT users.id as uid,username,balance,lifetimepaid,limitby,email,userpquota.id,lifepagecounter,pagecounter,softlimit,hardlimit,datelimit FROM users JOIN userpquota ON users.id=userpquota.userid AND printerid=%s ORDER BY username ASC" % self.doQuote(printer.ident))
+        result = self.doSearch("SELECT users.id as uid,username,balance,lifetimepaid,limitby,email,overcharge,userpquota.id,lifepagecounter,pagecounter,softlimit,hardlimit,datelimit,warncount FROM users JOIN userpquota ON users.id=userpquota.userid AND printerid=%s ORDER BY username ASC" % self.doQuote(printer.ident))
         if result :
             for record in result :
@@ -433,4 +446,5 @@
                     user.LifeTimePaid = record.get("lifetimepaid")
                     user.Email = record.get("email") 
+                    user.OverCharge = record.get("overcharge")
                     user.Exists = 1
                     userpquota = StorageUserPQuota(self, user, printer)
@@ -441,4 +455,5 @@
                     userpquota.HardLimit = record.get("hardlimit")
                     userpquota.DateLimit = record.get("datelimit")
+                    userpquota.WarnCount = record.get("warncount")
                     userpquota.Exists = 1
                     usersandquotas.append((user, userpquota))
@@ -466,5 +481,5 @@
     def addUser(self, user) :        
         """Adds a user to the quota storage, returns its id."""
-        self.doModify("INSERT INTO users (username, limitby, balance, lifetimepaid, email) VALUES (%s, %s, %s, %s, %s)" % (self.doQuote(user.Name), self.doQuote(user.LimitBy or 'quota'), self.doQuote(user.AccountBalance or 0.0), self.doQuote(user.LifeTimePaid or 0.0), self.doQuote(user.Email)))
+        self.doModify("INSERT INTO users (username, limitby, balance, lifetimepaid, email, overcharge) VALUES (%s, %s, %s, %s, %s, %s)" % (self.doQuote(user.Name), self.doQuote(user.LimitBy or 'quota'), self.doQuote(user.AccountBalance or 0.0), self.doQuote(user.LifeTimePaid or 0.0), self.doQuote(user.Email), self.doQuote(user.OverCharge)))
         return self.getUser(user.Name)
         
@@ -503,4 +518,8 @@
         self.doModify("UPDATE printers SET description=%s WHERE id=%s" % (self.doQuote(description), self.doQuote(printer.ident)))
         
+    def writeUserOverCharge(self, user, factor) :
+        """Sets the user's overcharging coefficient."""
+        self.doModify("UPDATE users SET overcharge=%s WHERE id=%s" % (self.doQuote(factor), self.doQuote(user.ident)))
+        
     def writeUserLimitBy(self, user, limitby) :    
         """Sets the user's limiting factor."""
@@ -521,13 +540,13 @@
     def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :    
         """Increase page counters for a user print quota."""
-        self.doModify("UPDATE userpquota SET pagecounter=pagecounter+%s,lifepagecounter=lifepagecounter+%s WHERE id=%s" % (self.doQuote(nbpages), self.doQuote(nbpages), self.doQuote(userpquota.ident)))
+        self.doModify("UPDATE userpquota SET pagecounter=pagecounter + %s,lifepagecounter=lifepagecounter + %s WHERE id=%s" % (self.doQuote(nbpages), self.doQuote(nbpages), self.doQuote(userpquota.ident)))
        
     def writeUserPQuotaPagesCounters(self, userpquota, newpagecounter, newlifepagecounter) :    
         """Sets the new page counters permanently for a user print quota."""
-        self.doModify("UPDATE userpquota SET pagecounter=%s, lifepagecounter=%s, datelimit=NULL WHERE id=%s" % (self.doQuote(newpagecounter), self.doQuote(newlifepagecounter), self.doQuote(userpquota.ident)))
+        self.doModify("UPDATE userpquota SET pagecounter=%s, lifepagecounter=%s, warncount=0, datelimit=NULL WHERE id=%s" % (self.doQuote(newpagecounter), self.doQuote(newlifepagecounter), self.doQuote(userpquota.ident)))
        
     def decreaseUserAccountBalance(self, user, amount) :    
         """Decreases user's account balance from an amount."""
-        self.doModify("UPDATE users SET balance=balance-%s WHERE id=%s" % (self.doQuote(amount), self.doQuote(user.ident)))
+        self.doModify("UPDATE users SET balance=balance - %s WHERE id=%s" % (self.doQuote(amount), self.doQuote(user.ident)))
        
     def writeUserAccountBalance(self, user, newbalance, newlifetimepaid=None) :    
@@ -565,5 +584,13 @@
     def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
         """Sets soft and hard limits for a user quota."""
-        self.doModify("UPDATE userpquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE id=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userpquota.ident)))
+        self.doModify("UPDATE userpquota SET softlimit=%s, hardlimit=%s, warncount=0, datelimit=NULL WHERE id=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userpquota.ident)))
+        
+    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
+        """Sets the warn counter value for a user quota."""
+        self.doModify("UPDATE userpquota SET warncount=%s WHERE id=%s" % (self.doQuote(warncount), self.doQuote(userpquota.ident)))
+        
+    def increaseUserPQuotaWarnCount(self, userpquota) :
+        """Increases the warn counter value for a user quota."""
+        self.doModify("UPDATE userpquota SET warncount=warncount+1 WHERE id=%s" % self.doQuote(userpquota.ident))
         
     def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
@@ -620,4 +647,7 @@
                 job.JobHostName = fields.get("hostname")
                 job.JobSizeBytes = fields.get("jobsizebytes")
+                job.JobMD5Sum = fields.get("md5sum")
+                job.JobPages = fields.get("pages")
+                job.JobBillingCode = fields.get("billingcode")
                 job.UserName = fields.get("username")
                 job.PrinterName = fields.get("printername")
Index: pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- pykota/trunk/pykota/storages/ldapstorage.py (revision 2042)
+++ pykota/trunk/pykota/storages/ldapstorage.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.100  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.99  2005/01/24 17:44:17  jalet
 # Same fix for group print quota entries wrt LDAP performance
@@ -647,17 +654,12 @@
         """Extracts user information given its name."""
         user = StorageUser(self, username)
-        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaUserName", "pykotaLimitBy", self.info["usermail"]], base=self.info["userbase"])
+        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaUserName", "pykotaLimitBy", self.info["usermail"], "pykotaOverCharge"], base=self.info["userbase"])
         if result :
             fields = result[0][1]
             user.ident = result[0][0]
             user.Name = fields.get("pykotaUserName", [username])[0] 
-            user.Email = fields.get(self.info["usermail"])
-            if user.Email is not None :
-                user.Email = user.Email[0]
-            user.LimitBy = fields.get("pykotaLimitBy")
-            if user.LimitBy is not None :
-                user.LimitBy = user.LimitBy[0]
-            else :    
-                user.LimitBy = "quota"
+            user.Email = fields.get(self.info["usermail"], [None])[0]
+            user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
+            user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
             result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["balancerdn"], username), ["pykotaBalance", "pykotaLifeTimePaid", "pykotaPayments"], base=self.info["balancebase"])
             if not result :
@@ -695,9 +697,5 @@
             group.ident = result[0][0]
             group.Name = fields.get("pykotaGroupName", [groupname])[0] 
-            group.LimitBy = fields.get("pykotaLimitBy")
-            if group.LimitBy is not None :
-                group.LimitBy = group.LimitBy[0]
-            else :    
-                group.LimitBy = "quota"
+            group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
             group.AccountBalance = 0.0
             group.LifeTimePaid = 0.0
@@ -717,6 +715,6 @@
             printer.ident = result[0][0]
             printer.Name = fields.get("pykotaPrinterName", [printername])[0] 
-            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0] or 0.0)
-            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0] or 0.0)
+            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0])
+            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0])
             printer.uniqueMember = fields.get("uniqueMember", [])
             printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
@@ -732,10 +730,11 @@
             else :    
                 base = self.info["userquotabase"]
-            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % (user.Name, printer.Name), ["pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=base)
+            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % (user.Name, printer.Name), ["pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], base=base)
             if result :
                 fields = result[0][1]
                 userpquota.ident = result[0][0]
-                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0] or 0)
-                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0] or 0)
+                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
+                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
+                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
                 userpquota.SoftLimit = fields.get("pykotaSoftLimit")
                 if userpquota.SoftLimit is not None :
@@ -815,5 +814,5 @@
             result = None
             try :
-                result = self.doSearch("objectClass=pykotaJob", ["pykotaJobSizeBytes", "pykotaHostName", "pykotaUserName", "pykotaJobId", "pykotaPrinterPageCounter", "pykotaJobSize", "pykotaAction", "pykotaJobPrice", "pykotaFileName", "pykotaTitle", "pykotaCopies", "pykotaOptions", "createTimestamp"], base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
+                result = self.doSearch("objectClass=pykotaJob", ["pykotaJobSizeBytes", "pykotaHostName", "pykotaUserName", "pykotaJobId", "pykotaPrinterPageCounter", "pykotaJobSize", "pykotaAction", "pykotaJobPrice", "pykotaFileName", "pykotaTitle", "pykotaCopies", "pykotaOptions", "pykotaBillingCode", "pykotaPages", "pykotaMD5Sum", "createTimestamp"], base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
             except PyKotaStorageError :    
                 pass # Last job entry exists, but job probably doesn't exist anymore. 
@@ -823,5 +822,5 @@
                 lastjob.JobId = fields.get("pykotaJobId")[0]
                 lastjob.UserName = fields.get("pykotaUserName")[0]
-                lastjob.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0] or 0)
+                lastjob.PrinterPageCounter = int(fields.get("pykotaPrinterPageCounter", [0])[0])
                 try :
                     lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0])
@@ -839,4 +838,7 @@
                 lastjob.JobHostName = fields.get("pykotaHostName", [""])[0]
                 lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
+                lastjob.JobBillingCode = fields.get("pykotaMD5Sum", [None])[0]
+                lastjob.JobMD5Sum = fields.get("pykotaMD5Sum", [None])[0]
+                lastjob.JobPages = fields.get("pykotaPages", [""])[0]
                 date = fields.get("createTimestamp", ["19700101000000"])[0]
                 year = int(date[:4])
@@ -924,5 +926,5 @@
         else :
            base = self.info["userquotabase"]
-        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaUserName=%s)" % uname for uname in names])), ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=base)
+        result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s)(|%s))" % (printer.Name, "".join(["(pykotaUserName=%s)" % uname for uname in names])), ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit", "pykotaWarnCount"], base=base)
         if result :
             for (userquotaid, fields) in result :
@@ -930,6 +932,7 @@
                 userpquota = StorageUserPQuota(self, user, printer)
                 userpquota.ident = userquotaid
-                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0] or 0)
-                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0] or 0)
+                userpquota.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
+                userpquota.LifePageCounter = int(fields.get("pykotaLifePageCounter", [0])[0])
+                userpquota.WarnCount = int(fields.get("pykotaWarnCount", [0])[0])
                 userpquota.SoftLimit = fields.get("pykotaSoftLimit")
                 if userpquota.SoftLimit is not None :
@@ -990,4 +993,5 @@
                        "pykotaUserName" : user.Name,
                        "pykotaLimitBy" : (user.LimitBy or "quota"),
+                       "pykotaOverCharge" : str(user.OverCharge),
                     }   
                        
@@ -1048,5 +1052,5 @@
         newfields = { 
                       "pykotaGroupName" : group.Name,
-                      "pykotaLimitBY" : (group.LimitBy or "quota"),
+                      "pykotaLimitBy" : (group.LimitBy or "quota"),
                     } 
         mustadd = 1
@@ -1102,4 +1106,5 @@
                    "pykotaPageCounter" : "0",
                    "pykotaLifePageCounter" : "0",
+                   "pykotaWarnCount" : "0",
                  } 
         if self.info["userquotabase"].lower() == "user" :
@@ -1141,4 +1146,11 @@
         self.doModify(printer.ident, fields)
         
+    def writeUserOverCharge(self, user, factor) :
+        """Sets the user's overcharging coefficient."""
+        fields = {
+                   "pykotaOverCharge" : str(factor),
+                 }
+        self.doModify(user.ident, fields)
+        
     def writeUserLimitBy(self, user, limitby) :    
         """Sets the user's limiting factor."""
@@ -1183,4 +1195,5 @@
                    "pykotaLifePageCounter" : str(newlifepagecounter),
                    "pykotaDateLimit" : None,
+                   "pykotaWarnCount" : "0",
                  }  
         return self.doModify(userpquota.ident, fields)         
@@ -1246,4 +1259,5 @@
                    "pykotaHostName" : str(clienthost), 
                    "pykotaJobSizeBytes" : str(jobsizebytes),
+                   # TODO : add the 3 missing fields
                  }
         if (not self.disablehistory) or (not printer.LastJob.Exists) :
@@ -1278,6 +1292,21 @@
                    "pykotaHardLimit" : str(hardlimit),
                    "pykotaDateLimit" : "None",
+                   "pykotaWarnCount" : "0",
                  }
         self.doModify(userpquota.ident, fields)
+        
+    def writeUserPQuotaWarnCount(self, userpquota, warncount) :
+        """Sets the warn counter value for a user quota."""
+        fields = { 
+                   "pykotaWarnCount" : str(warncount or 0),
+                 }
+        self.doModify(userpquota.ident, fields)
+        
+    def increaseUserPQuotaWarnCount(self, userpquota) :
+        """Increases the warn counter value for a user quota."""
+        fields = {
+                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
+                 }
+        return self.doModify(userpquota.ident, fields)         
         
     def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
Index: pykota/trunk/pykota/reporter.py
===================================================================
--- pykota/trunk/pykota/reporter.py (revision 1692)
+++ pykota/trunk/pykota/reporter.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.11  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.10  2004/09/02 10:09:30  jalet
 # Fixed bug in LDAP user deletion code which didn't correctly delete the user's
@@ -87,7 +94,7 @@
     def getReportHeader(self) :        
         if self.isgroup :
-            return _("Group           used    soft    hard    balance grace         total       paid")
+            return _("Group          overcharge   used    soft    hard    balance grace         total       paid warn")
         else :    
-            return _("User            used    soft    hard    balance grace         total       paid")
+            return _("User           overcharge   used    soft    hard    balance grace         total       paid warn")
             
     def getPrinterRealPageCounter(self, printer) :        
@@ -109,4 +116,12 @@
         balance = float(entry.AccountBalance or 0.0)
         lifetimepaid = float(entry.LifeTimePaid or 0.0)
+        if not hasattr(entry, "OverCharge") :
+            overcharge = _("N/A")       # Not available for groups
+        else :    
+            overcharge = float(entry.OverCharge or 0.0)
+        if not hasattr(quota, "WarnCount") :    
+            warncount = _("N/A")        # Not available for groups
+        else :    
+            warncount = int(quota.WarnCount or 0)
         
         #balance
@@ -199,5 +214,10 @@
         strbalance = ("%5.2f" % balance)[:10]
         strlifetimepaid = ("%6.2f" % lifetimepaid)[:10]
-        return (lifepagecounter, lifetimepaid, entry.Name, reached, pagecounter, str(quota.SoftLimit), str(quota.HardLimit), strbalance, str(datelimit)[:10], lifepagecounter, strlifetimepaid)
+        strovercharge = ("%5s" % overcharge)[:5]
+        strwarncount = ("%4s" % warncount)[:4]
+        return (lifepagecounter, lifetimepaid, entry.Name, reached, \
+                pagecounter, str(quota.SoftLimit), str(quota.HardLimit), \
+                strbalance, str(datelimit)[:10], lifepagecounter, \
+                strlifetimepaid, strovercharge, strwarncount)
         
 def openReporter(tool, reporttype, printers, ugnames, isgroup) :
Index: pykota/trunk/pykota/storage.py
===================================================================
--- pykota/trunk/pykota/storage.py (revision 2030)
+++ pykota/trunk/pykota/storage.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.68  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.67  2005/01/18 19:47:50  jalet
 # Big bug fix wrt the datelimit attribute
@@ -276,4 +283,5 @@
         self.LifeTimePaid = None
         self.Email = None
+        self.OverCharge = 1.0
         self.Payments = [] # TODO : maybe handle this smartly for SQL, for now just don't retrieve them
         
@@ -308,4 +316,9 @@
             self.LimitBy = limitby
         
+    def setOverChargeFactor(self, factor) :    
+        """Sets the user's overcharging coefficient."""
+        self.parent.writeUserOverCharge(self, factor)
+        self.OverCharge = factor
+        
     def delete(self) :    
         """Deletes an user from the Quota Storage."""
@@ -357,4 +370,5 @@
         self.PricePerJob = None
         self.Description = None
+        self.Coefficients = None
         
     def __getattr__(self, name) :    
@@ -424,4 +438,5 @@
         self.HardLimit = None
         self.DateLimit = None
+        self.WarnCount = None
         
     def __getattr__(self, name) :    
@@ -445,4 +460,5 @@
         self.HardLimit = hardlimit
         self.DateLimit = None
+        self.WarnCount = 0
         
     def setUsage(self, used) :
@@ -454,4 +470,5 @@
                 self.parent.increaseUserPQuotaPagesCounters(self, vused)
                 self.parent.writeUserPQuotaDateLimit(self, None)
+                self.parent.writeUserPQuotaWarnCount(self, 0)
             except PyKotaStorageError, msg :    
                 self.parent.rollbackTransaction()
@@ -465,5 +482,11 @@
             self.PageCounter = self.LifePageCounter = vused
         self.DateLimit = None
+        self.WarnCount = 0
 
+    def warn(self) :
+        """Increases the warn counter for this user quota."""
+        self.parent.increaseUserPQuotaWarnCount(self)
+        self.WarnCount = (self.WarnCount or 0) + 1
+        
     def reset(self) :    
         """Resets page counter to 0."""
@@ -482,8 +505,14 @@
         totalprice = 0.0    
         if jobsize :
-            for upq in [ self ] + self.ParentPrintersUserPQuota :
-                price = (float(upq.Printer.PricePerPage or 0.0) * jobsize) + float(upq.Printer.PricePerJob or 0.0)
-                totalprice += price
-        return totalprice    
+            if self.User.OverCharge != 0.0 :    # optimization, but TODO : beware of rounding errors
+                for upq in [ self ] + self.ParentPrintersUserPQuota :
+                    price = (float(upq.Printer.PricePerPage or 0.0) * jobsize) + float(upq.Printer.PricePerJob or 0.0)
+                    totalprice += price
+        if self.User.OverCharge != 1.0 : # TODO : beware of rounding errors
+            overcharged = totalprice * self.User.OverCharge        
+            self.parent.tool.printInfo("Overcharging %s by a factor of %s ===> User %s will be charged for %s units." % (totalprice, self.User.OverCharge, self.User.Name, overcharged))
+            return overcharged
+        else :    
+            return totalprice
             
     def increasePagesUsage(self, jobsize) :
@@ -589,4 +618,7 @@
         self.JobOptions = None
         self.JobHostName = None
+        self.JobMD5Sum = None
+        self.JobPages = None
+        self.JobBillingCode = None
         
     def __getattr__(self, name) :    
Index: pykota/trunk/pykota/config.py
===================================================================
--- pykota/trunk/pykota/config.py (revision 1968)
+++ pykota/trunk/pykota/config.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.59  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.58  2004/12/02 22:01:58  jalet
 # TLS is now supported with the LDAP backend
@@ -507,4 +514,19 @@
             return (mailto, args)
         
+    def getMaxDenyBanners(self, printername) :    
+        """Returns the maximum number of deny banners to be printed for a particular user on a particular printer."""
+        try :
+            maxdb = self.getPrinterOption(printername, "maxdenybanners")
+        except PyKotaConfigError :    
+            return 0 # default value is to forbid printing a deny banner.
+        try :
+            value = int(maxdb.strip())
+            if value < 0 :
+                raise ValueError
+        except (TypeError, ValueError) :    
+            raise PyKotaConfigError, _("Invalid maximal deny banners counter %s") % maxdb
+        else :    
+            return value
+            
     def getGraceDelay(self, printername) :    
         """Returns the grace delay in days."""
Index: pykota/trunk/pykota/tool.py
===================================================================
--- pykota/trunk/pykota/tool.py (revision 2008)
+++ pykota/trunk/pykota/tool.py (revision 2054)
@@ -22,4 +22,11 @@
 #
 # $Log$
+# Revision 1.149  2005/02/13 22:02:29  jalet
+# Big database structure changes. Upgrade script is now included as well as
+# the new LDAP schema.
+# Introduction of the -o | --overcharge command line option to edpykota.
+# The output of repykota is more complete, but doesn't fit in 80 columns anymore.
+# Introduction of the new 'maxdenybanners' directive.
+#
 # Revision 1.148  2005/01/06 23:24:21  jalet
 # Regain priviledge the time to open the job's data file when printing in
@@ -1012,16 +1019,20 @@
                 return action        
             else :    
-                val = float(user.AccountBalance or 0.0)
-                enforcement = self.config.getPrinterEnforcement(printer.Name)
-                if enforcement == "STRICT" : 
-                    val -= self.softwareJobPrice # use precomputed size.
-                if val <= 0.0 :
-                    action = "DENY"
-                elif val <= self.config.getPoorMan() :    
-                    action = "WARN"
+                if user.OverCharge == 0.0 :
+                    self.printInfo(_("User %s will not be charged for printing.") % user.Name)
+                    action = "ALLOW"
                 else :
-                    action = "ALLOW"
-                if (enforcement == "STRICT") and (val == 0.0) :
-                    action = "WARN" # we can still print until account is 0
+                    val = float(user.AccountBalance or 0.0)
+                    enforcement = self.config.getPrinterEnforcement(printer.Name)
+                    if enforcement == "STRICT" : 
+                        val -= self.softwareJobPrice # use precomputed size.
+                    if val <= 0.0 :
+                        action = "DENY"
+                    elif val <= self.config.getPoorMan() :    
+                        action = "WARN"
+                    else :
+                        action = "ALLOW"
+                    if (enforcement == "STRICT") and (val == 0.0) :
+                        action = "WARN" # we can still print until account is 0
                 return action    
         else :
@@ -1106,4 +1117,5 @@
         if action.startswith("POLICY_") :
             action = action[7:]
+            
         if action == "DENY" :
             adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
@@ -1286,4 +1298,5 @@
     def exportUserInfo(self, userpquota) :
         """Exports user information to the environment."""
+        os.environ["PYKOTAOVERCHARGE"] = str(userpquota.User.OverCharge)
         os.environ["PYKOTALIMITBY"] = str(userpquota.User.LimitBy)
         os.environ["PYKOTABALANCE"] = str(userpquota.User.AccountBalance or 0.0)
@@ -1294,4 +1307,5 @@
         os.environ["PYKOTAHARDLIMIT"] = str(userpquota.HardLimit)
         os.environ["PYKOTADATELIMIT"] = str(userpquota.DateLimit)
+        os.environ["PYKOTAWARNCOUNT"] = str(userpquota.WarnCount)
         
         # not really an user information, but anyway
Index: pykota/trunk/pykota/version.py
===================================================================
--- pykota/trunk/pykota/version.py (revision 2044)
+++ pykota/trunk/pykota/version.py (revision 2054)
@@ -22,5 +22,5 @@
 #
 
-__version__ = "1.21alpha23_unofficial"
+__version__ = "1.21alpha24_unofficial"
 
 __doc__ = """PyKota : a complete Printing Quota Solution for CUPS and LPRng."""
