Index: /pykota/trunk/pykota/tool.py
===================================================================
--- /pykota/trunk/pykota/tool.py (revision 2762)
+++ /pykota/trunk/pykota/tool.py (revision 2765)
@@ -201,4 +201,8 @@
             sys.stdout.flush()
             
+    def done(self) :    
+        """Displays the 'done' message."""
+        self.display("\r100.00%%\r        \r%s\n" % _("Done."))
+        
     def logdebug(self, message) :    
         """Logs something to debug output if debug is enabled."""
Index: /pykota/trunk/pykota/storages/sql.py
===================================================================
--- /pykota/trunk/pykota/storages/sql.py (revision 2763)
+++ /pykota/trunk/pykota/storages/sql.py (revision 2765)
@@ -532,8 +532,19 @@
         return self.getPrinter(printername)
         
-    def addBillingCode(self, label) :        
-        """Adds a billing code to the quota storage, returns it."""
-        self.doModify("INSERT INTO billingcodes (billingcode) VALUES (%s)" % self.doQuote(self.userCharsetToDatabase(label)))
-        return self.getBillingCode(label)
+    def addBillingCode(self, bcode) :
+        """Adds a billing code to the quota storage, returns the old value if it already exists."""
+        try :
+            self.doModify("INSERT INTO billingcodes (billingcode, balance, pagecounter, description) VALUES (%s, %s, %s, %s)" \
+                               % (self.doQuote(self.userCharsetToDatabase(bcode.BillingCode)), 
+                                  self.doQuote(bcode.Balance or 0.0), \
+                                  self.doQuote(bcode.PageCounter or 0), \
+                                  self.doQuote(self.userCharsetToDatabase(bcode.Description))))
+        except PyKotaStorageError :    
+            # TODO : check if this is an error different from a duplicate insert
+            # return the existing entry which has to be modified
+            return self.getBillingCode(bcode.BillingCode)
+        else :    
+            bcode.isDirty = False
+            return None # the entry created doesn't need further modification
         
     def addUser(self, user) :        
@@ -651,15 +662,15 @@
         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 saveBillingCode(self, code) :    
+    def saveBillingCode(self, bcode) :    
         """Saves the billing code to the database."""
         self.doModify("UPDATE billingcodes SET balance=%s, pagecounter=%s, description=%s WHERE id=%s" \
-                            % (self.doQuote(code.Balance or 0.0), \
-                               self.doQuote(code.PageCounter or 0), \
-                               self.doQuote(self.userCharsetToDatabase(code.Description)), \
-                               self.doQuote(code.ident)))
+                            % (self.doQuote(bcode.Balance or 0.0), \
+                               self.doQuote(bcode.PageCounter or 0), \
+                               self.doQuote(self.userCharsetToDatabase(bcode.Description)), \
+                               self.doQuote(bcode.ident)))
        
-    def consumeBillingCode(self, code, pagecounter, balance) :
+    def consumeBillingCode(self, bcode, pagecounter, balance) :
         """Consumes from a billing code."""
-        self.doModify("UPDATE billingcodes SET balance=balance + %s, pagecounter=pagecounter + %s WHERE id=%s" % (self.doQuote(balance), self.doQuote(pagecounter), self.doQuote(code.ident)))
+        self.doModify("UPDATE billingcodes SET balance=balance + %s, pagecounter=pagecounter + %s WHERE id=%s" % (self.doQuote(balance), self.doQuote(pagecounter), self.doQuote(bcode.ident)))
        
     def decreaseUserAccountBalance(self, user, amount) :    
@@ -813,29 +824,37 @@
             self.commitTransaction()
             
-    def deleteManyBillingCodesFromNames(self, billingcodes) :        
-        """Deletes many billing codes from their names."""
-        codelabels = ", ".join(["%s" % self.doQuote(b) for b in billingcodes])
+    def deleteManyBillingCodes(self, billingcodes) :        
+        """Deletes many billing codes."""
+        codeids = ", ".join(["%s" % self.doQuote(b.ident) for b in billingcodes])
         self.deleteInTransaction([ 
-                    "DELETE FROM billingcodes WHERE billingcode IN (%s)" % codelabels,])
-            
-    def deleteManyUsersFromNames(self, usernames) :        
-        """Deletes many users from their names."""
-        usernames = ", ".join(["%s" % self.doQuote(u) for u in usernames])
+                    "DELETE FROM billingcodes WHERE id IN (%s)" % codeids,])
+            
+    def deleteManyUsers(self, users) :        
+        """Deletes many users."""
+        userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users])
         self.deleteInTransaction([ 
-                    "DELETE FROM payments WHERE userid IN (SELECT id FROM users WHERE username IN %s)" % usernames,
-                    "DELETE FROM groupsmembers WHERE userid IN (SELECT id FROM users WHERE username IN %s)" % usernames,
-                    "DELETE FROM jobhistory WHERE userid IN (SELECT id FROM users WHERE username IN %s)" % usernames,
-                    "DELETE FROM userpquota WHERE userid IN (SELECT id FROM users WHERE username IN %s)" % usernames,
-                    "DELETE FROM users WHERE username IN %s" % usernames,])
-        
-    def deleteManyPrintersFromNames(self, printernames) :        
-        """Deletes many printers from their names."""
-        printernames = ", ".join(["%s" % self.doQuote(p) for p in printernames])
+                    "DELETE FROM payments WHERE userid IN (%s)" % userids,
+                    "DELETE FROM groupsmembers WHERE userid IN (%s)" % userids,
+                    "DELETE FROM jobhistory WHERE userid IN (%s)" % userids,
+                    "DELETE FROM userpquota WHERE userid IN (%s)" % userids,
+                    "DELETE FROM users WHERE id IN (%s)" % userids,])
+                    
+    def deleteManyGroups(self, groups) :        
+        """Deletes many groups."""
+        groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups])
         self.deleteInTransaction([ 
-                    "DELETE FROM printergroupsmembers WHERE groupid=%s OR printerid=%s" % (self.doQuote(printer.ident), self.doQuote(printer.ident)),
-                    "DELETE FROM jobhistory WHERE printerid=%s" % self.doQuote(printer.ident),
-                    "DELETE FROM grouppquota WHERE printerid=%s" % self.doQuote(printer.ident),
-                    "DELETE FROM userpquota WHERE printerid=%s" % self.doQuote(printer.ident),
-                    "DELETE FROM printers WHERE id=%s" % self.doQuote(printer.ident),])
+                    "DELETE FROM groupsmembers WHERE groupid IN (%s)" % groupids,
+                    "DELETE FROM grouppquota WHERE groupid IN (%s)" % groupids,
+                    "DELETE FROM groups WHERE id IN (%s)" % groupids,])
+        
+    def deleteManyPrinters(self, printers) :
+        """Deletes many printers."""
+        printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers])
+        self.deleteInTransaction([ 
+                    "DELETE FROM printergroupsmembers WHERE groupid IN (%s) OR printerid IN (%s)" % printerids,
+                    "DELETE FROM jobhistory WHERE printerid IN (%s)" % printerids,
+                    "DELETE FROM grouppquota WHERE printerid IN (%s)" % printerids,
+                    "DELETE FROM userpquota WHERE printerid IN (%s)" % printerids,
+                    "DELETE FROM printers WHERE id IN (%s)" % printerids,])
         
     def deleteManyUserPQuotas(self, printers, users) :        
Index: /pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- /pykota/trunk/pykota/storages/ldapstorage.py (revision 2763)
+++ /pykota/trunk/pykota/storages/ldapstorage.py (revision 2765)
@@ -1462,22 +1462,22 @@
                 self.doDelete(group.ident)
                 
-    def deleteManyBillingCodesFromNames(self, billingcodes) :        
-        """Deletes many billing codes from their names."""
-        for bcode in self.getMatchingBillingCodes(",".join(billingcodes)) :
+    def deleteManyBillingCodes(self, billingcodes) :
+        """Deletes many billing codes."""
+        for bcode in billingcodes :
             bcode.delete()
         
-    def deleteManyUsersFromNames(self, usernames) :        
-        """Deletes many users from their names."""
-        for user in self.getMatchingUsers(",".join(usernames)) : 
+    def deleteManyUsers(self, users) :        
+        """Deletes many users."""
+        for user in users :
             user.delete()
             
-    def deleteManyGroupsFromNames(self, groupnames) :        
-        """Deletes many groups from their names."""
-        for group in self.getMatchingGroups(",".join(groupnames)) : 
+    def deleteManyGroups(self, groups) :        
+        """Deletes many groups."""
+        for group in groups :
             group.delete()
         
-    def deleteManyPrintersFromNames(self, printernames) :        
-        """Deletes many printers from their names."""
-        for printer in self.getMatchingPrinters(",".join(printernames)) :
+    def deleteManyPrinters(self, printers) :        
+        """Deletes many printers."""
+        for printer in printers :
             printer.delete()
         
@@ -1701,25 +1701,30 @@
         return code    
         
-    def addBillingCode(self, label) :
+    def addBillingCode(self, bcode) :
         """Adds a billing code to the quota storage, returns it."""
+        oldentry = self.getBillingCode(bcode.BillingCode)
+        if oldentry.Exists :
+            return oldentry # we return the existing entry
         uuid = self.genUUID()
         dn = "cn=%s,%s" % (uuid, self.info["billingcodebase"])
         fields = { "objectClass" : ["pykotaObject", "pykotaBilling"],
                    "cn" : uuid,
-                   "pykotaBillingCode" : self.userCharsetToDatabase(label),
-                   "pykotaPageCounter" : "0",
-                   "pykotaBalance" : "0.0",
+                   "pykotaBillingCode" : self.userCharsetToDatabase(bcode.BillingCode),
+                   "pykotaPageCounter" : str(bcode.PageCounter or 0),
+                   "pykotaBalance" : str(bcode.Balance or 0.0),
+                   "description" : self.userCharsetToDatabase(bcode.Description or ""), 
                  } 
         self.doAdd(dn, fields)
-        return self.getBillingCode(label)
-        
-    def saveBillingCode(self, code) :
+        bcode.isDirty = False
+        return None # the entry created doesn't need further modification
+        
+    def saveBillingCode(self, bcode) :
         """Sets the new description for a billing code."""
         fields = {
-                   "description" : self.userCharsetToDatabase(code.Description or ""), 
-                   "pykotaPageCounter" : str(code.PageCounter),
-                   "pykotaBalance" : str(code.Balance),
+                   "description" : self.userCharsetToDatabase(bcode.Description or ""), 
+                   "pykotaPageCounter" : str(bcode.PageCounter or 0),
+                   "pykotaBalance" : str(bcode.Balance or 0.0),
                  }
-        self.doModify(code.ident, fields)
+        self.doModify(bcode.ident, fields)
             
     def getMatchingBillingCodes(self, billingcodepattern) :
@@ -1744,5 +1749,5 @@
         return codes        
         
-    def consumeBillingCode(self, code, pagecounter, balance) :
+    def consumeBillingCode(self, bcode, pagecounter, balance) :
         """Consumes from a billing code."""
         fields = {
@@ -1750,4 +1755,4 @@
                    "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int },
                  }
-        return self.doModify(code.ident, fields)         
+        return self.doModify(bcode.ident, fields)         
 
Index: /pykota/trunk/bin/edpykota
===================================================================
--- /pykota/trunk/bin/edpykota (revision 2762)
+++ /pykota/trunk/bin/edpykota (revision 2765)
@@ -185,5 +185,5 @@
             getattr(self.storage, "deleteMany%sPQuotas" % suffix)(printers, entries)
             self.display("\n")
-            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
+            self.done()
         elif options["list"] :
             for printer in printers :
@@ -305,6 +305,5 @@
                             percent = 100.0 * float(i) / float(nbtotal)
                             self.display("\r%.02f%%" % percent)
-            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
-                     
+            self.done()         
 if __name__ == "__main__" : 
     retcode = 0
Index: /pykota/trunk/bin/pkbcodes
===================================================================
--- /pykota/trunk/bin/pkbcodes (revision 2725)
+++ /pykota/trunk/bin/pkbcodes (revision 2765)
@@ -30,4 +30,5 @@
 
 from pykota.tool import PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
+from pykota.storage import StorageBillingCode
 
 __doc__ = N_("""pkbcodes v%(__version__)s (c) %(__years__)s %(__author__)s
@@ -84,4 +85,11 @@
 class PKBcodes(PyKotaTool) :        
     """A class for a billing codes manager."""
+    def modifyBillingCode(self, billingcode, reset, description) :
+        """Modifies a billing code."""
+        if reset :
+            billingcode.reset()    
+        if description is not None : # NB : "" is allowed !
+            billingcode.setDescription(description)
+        
     def main(self, names, options) :
         """Manage billing codes."""
@@ -89,44 +97,40 @@
             raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
             
+        if not options["add"] :
+            if not options["list"] :
+                self.display(_("Extracting datas..."))
+            if not names :      # NB : can't happen for --delete because it's catched earlier
+                names = ["*"]
+            billingcodes = self.storage.getMatchingBillingCodes(",".join(names))
+            if not billingcodes :
+                raise PyKotaCommandLineError, _("There's no billingcode matching %s") % " ".join(names)
+                        
+        reset = options["reset"]
+        description = options["description"]
+        if description :
+            description = options["description"].strip()
+                
         if options["delete"] :    
-            self.display("%s...\n" % _("Deletion"))
-            todelete = self.storage.getMatchingBillingCodes(",".join(names))
-            nbtotal = len(todelete)
+            self.display("\n%s..." % _("Deletion"))
+            self.storage.deleteManyBillingCodes(billingcodes)
+            self.display("\n")
+        elif options["add"] :    
+            self.display("%s...\n" % _("Creation"))
+            nbtotal = len(names)
             for i in range(nbtotal) :
-                entry = todelete[i]
-                if entry.Exists :
-                    entry.delete()
+                bname = names[i]
+                billingcode = StorageBillingCode(self.storage, bname)
+                self.modifyBillingCode(billingcode, reset, description)
+                oldbillingcode = self.storage.addBillingCode(billingcode)
+                if oldbillingcode is not None :
+                    if options["skipexisting"] :
+                        self.printInfo(_("Billing code [%s] already exists, skipping.") % bname)
+                    else :    
+                        self.printInfo(_("Billing code [%s] already exists, will be modified.") % bname)
+                        self.modifyBillingCode(oldbillingcode, reset, description)
+                        oldbillingcode.save()
                 percent = 100.0 * float(i) / float(nbtotal)
                 self.display("\r%.02f%%" % percent)
-        else :
-            if options["add"] :    
-                self.display("%s...\n" % _("Creation"))
-                billingcodes = []
-                nbtotal = len(names)
-                for i in range(nbtotal) :
-                    bname = names[i]
-                    billingcode = self.storage.getBillingCode(bname)
-                    if billingcode.Exists :
-                        if options["skipexisting"] :
-                            self.printInfo(_("Billing code [%s] already exists, skipping.") % billingcode.BillingCode)
-                        else :    
-                            self.printInfo(_("Billing code [%s] already exists, will be modified.") % billingcode.BillingCode)
-                            billingcodes.append(billingcode)
-                    else :
-                        billingcode = self.storage.addBillingCode(bname)
-                        if not billingcode.Exists :
-                            raise PyKotaToolError, _("Impossible to add billingcode %s") % bname
-                        else :     
-                            billingcodes.append(billingcode)
-                    percent = 100.0 * float(i) / float(nbtotal)
-                    self.display("\r%.02f%%" % percent)
-                self.display("\r100.00%%\r        \r%s\n" % _("Done."))
-            else :        
-                if not names :
-                    names = ["*"]
-                billingcodes = self.storage.getMatchingBillingCodes(",".join(names))
-                if not billingcodes :
-                    raise PyKotaCommandLineError, _("There's no billingcode matching %s") % " ".join(names)
-                        
+        else :        
             if options["list"] :
                 for billingcode in billingcodes :
@@ -140,22 +144,15 @@
                            _("credits"))
             else :               
-                self.display("%s...\n" % _("Modification"))
-                reset = options["reset"]
-                if reset or options["description"] : # optimize when nothing to do
-                    if options["description"] :
-                        description = options["description"].strip()
-                    nbtotal = len(billingcodes)            
-                    for i in range(nbtotal) :        
-                        billingcode = billingcodes[i]
-                        if reset :
-                            billingcode.reset()    
-                        if description is not None : # NB : "" is allowed !
-                            billingcode.setDescription(description)
-                        billingcode.save()    
-                        percent = 100.0 * float(i) / float(nbtotal)
-                        self.display("\r%.02f%%" % percent)
-                        
+                self.display("\n%s...\n" % _("Modification"))
+                nbtotal = len(billingcodes)            
+                for i in range(nbtotal) :        
+                    billingcode = billingcodes[i]
+                    self.modifyBillingCode(billingcode, reset, description)
+                    billingcode.save()    
+                    percent = 100.0 * float(i) / float(nbtotal)
+                    self.display("\r%.02f%%" % percent)
+                    
         if not options["list"] :                
-            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
+            self.done()
                      
 if __name__ == "__main__" : 
Index: /pykota/trunk/bin/pkusers
===================================================================
--- /pykota/trunk/bin/pkusers (revision 2762)
+++ /pykota/trunk/bin/pkusers (revision 2765)
@@ -226,5 +226,5 @@
                     percent = 100.0 * float(i) / float(nbtotal)
                     self.display("\r%.02f%%" % percent)
-                self.display("\r100.00%%\r        \r%s\n" % _("Done."))
+                self.done()
             else :        
                 entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
@@ -353,5 +353,5 @@
                                 
         if not options["list"] :                
-            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
+            self.done()
                      
 if __name__ == "__main__" : 
Index: /pykota/trunk/NEWS
===================================================================
--- /pykota/trunk/NEWS (revision 2762)
+++ /pykota/trunk/NEWS (revision 2765)
@@ -24,4 +24,6 @@
     - 1.24alpha15 :
         
+        - optimized pkbcodes using edpykota as the model.
+        
         - 'edpykota --list' and 'pkusers --list' are now authorized 
           to mere mortal users, but the list is restricted to informations
