move invoice mailing to gacksbilling.py, lookup contact names when no billing contact...
smbaker [Thu, 18 Oct 2012 01:12:15 +0000 (18:12 -0700)]
apps/gacks/API.py
apps/gacks/gacksbilling.py

index 90f1e4e..9f42aeb 100644 (file)
@@ -1015,17 +1015,7 @@ class RemoteApi(AuthenticatedApi):
     def admin_mail_invoices(self, authToken_str, filter={}):
         (authToken, callerGID, objectGID) = self.authenticateToken(authToken_str, [])
 
-        what_i_did = {}
-        what_i_did["_filter"] = filter
-
-        accounts = self.accounts.get_accounts(filter)
-        for account in accounts:
-            if account.billingContacts:
-                email_list = [x.strip() for x in account.billingContacts.split(",")]
-                if (self.invoices.mail_invoice(account.name, email_list = email_list)):
-                    what_i_did[account.name] = email_list
-
-        return what_i_did
+        return self.billing.mail_invoices(filter)
 
 
 
index c18d382..0954ddb 100644 (file)
@@ -1,9 +1,14 @@
+import datetime
+from email.mime.text import MIMEText
+import hashlib
 import logging
+from subprocess import Popen, PIPE
 import time
 import gacksaccount
 import gacksinvoice
 import gackspolicy
-from gacksinvoice import GacksInvoiceManager, STATE_PENDING, STATE_AGGREGATED, KIND_CYCLE_CHARGE, KIND_SLOT_CHARGE, KIND_CYCLE_AGGREGATE
+from gacksconfig import GacksConfig
+from gacksinvoice import GacksInvoiceManager, STATE_INVOICED, STATE_PENDING, STATE_AGGREGATED, KIND_CYCLE_CHARGE, KIND_SLOT_CHARGE, KIND_CYCLE_AGGREGATE
 from gackstime import GacksTime
 
 glo_logger_name = "gacksapi"
@@ -18,10 +23,30 @@ class GacksBilling:
        self.invoices = invoices
        self.policies = policies
        self.resources = resources
+       self.config = GacksConfig()
 
    def mylogger(self):
        return logging.getLogger(glo_logger_name)
 
+   def get_handlers(self):
+       handlers = []
+       for resource in self.resources.resources:
+           handler = resource.get_handler_obj()
+           if handler is not None:
+               handlers.append(handler)
+
+       return handlers
+
+   def get_default_billing_emails(self, account_name):
+       emails = []
+       for handler in self.get_handlers():
+           persons = handler.getPersons(account_name)
+           for person in persons:
+               email = person['email']
+               if email not in emails:
+                   emails.append(email)
+       return emails
+
    def bill_membership(self):
        self.mylogger().info("doing bill_membship")
 
@@ -42,11 +67,7 @@ class GacksBilling:
                                               "kind_id": [KIND_CYCLE_CHARGE, KIND_SLOT_CHARGE, KIND_CYCLE_AGGREGATE]},
                                              lookup_objects=True)
 
-       handlers = []
-       for resource in self.resources.resources:
-           handler = resource.get_handler_obj()
-           if handler is not None:
-               handlers.append(handler)
+       handlers = self.get_handlers()
 
        for charge in inv.charges:
            account_name = charge.account_name
@@ -88,6 +109,110 @@ class GacksBilling:
             else:
                 self.mylogger().info("service on %s is still active (until %s)" % (acct.name, time.ctime(endDate)))
 
+   def generate_invoice_email(self, account, end_date=None, days=7):
+        if end_date==None:
+            now = datetime.datetime.now()
+            if now.weekday()!=6:
+                # if it's not sunday, then find the last sunday
+                last_sunday = now - datetime.timedelta(days=now.weekday()+1)
+            else:
+                last_sunday = now
+            end_date = time.mktime(last_sunday.timetuple())
+
+        # round it to the nearest day
+        end_date = time.mktime(datetime.datetime.fromtimestamp(end_date).date().timetuple())
+
+        # include all time until the last minute of that day
+        end_date = end_date + 24*60*60 - 1
+
+        # add one second, since start date is a >= relation
+        start_date = end_date - days * 24*60*60 + 1
+
+        start_date_str = str(datetime.date.fromtimestamp(start_date))
+        end_date_str = str(datetime.date.fromtimestamp(end_date))
+
+        filter = {"account": account.name, "start_date": start_date, "end_date": end_date, "state": STATE_INVOICED}
+        invoice = self.invoices.get_invoice_prime(filter)
+        summary = invoice.get_summary()
+
+        gacks_secret = self.config.get("gacks", "account_secret")
+        account_token=hashlib.sha1(gacks_secret+account.name).hexdigest()[:8]
+
+        has_activity = (summary["charges"] > 0) or (summary["credits"] > 0)
+
+        text = ""
+        text += "This email confirms that your latest billing statement for the account %s is now available on Vicci.org. " % account.name
+        text += "This statement covers the period from %s to %s.\n\n" % (start_date_str, end_date_str)
+        text += "Unique best effort machines used: %d\n" % summary["be_machines"]
+        text += "Total est effort core-hours used: %0.2f\n" % summary["be_core_hours"]
+        text += "Unique reservation machines used: %d\n" % summary["resv_machines"]
+        text += "Total charges: $ %0.2f\n" % summary["charges"]
+        text += "Total credits: $ %0.2f\n\n" % summary["credits"]
+        text += "Billing contacts for this account are: " + account.billingContacts + ". "
+        text += "Please see the Invoice page on the Vicci web site for detailed information:\n\n"
+        text += "https://vicci.org/db/gacks/invoices.php?account_name=%s&start_date=%d&end_date=%d&state=3&account_token=%s\n\n" % (account.name, start_date, end_date, account_token)
+        text += "Sincerely,\n"
+        text += "Vicci.org\n\n"
+        return (text, has_activity)
+
+   def mail_invoice(self, account, end_date=None, days=7, email_list=[], send_blank=False):
+        (invoice_email, has_activity) = self.generate_invoice_email(account, end_date, days)
+
+        if (not has_activity) and (not send_blank):
+            return False
+
+        for email in email_list:
+            msg = MIMEText(invoice_email)
+            msg["From"] = "billing@vicci.org"
+            msg["To"] = email
+            msg["Subject"] = "Vicci Invoice"
+            p = Popen(["/usr/sbin/sendmail", "-t"], stdin=PIPE)
+            p.communicate(msg.as_string())
+
+        # return True if invoices mailed
+        return True
+
+   def is_billable_account(self, name):
+       if name.startswith("system/"):
+           return False
+
+       if name.startswith("libvert_"):
+           return False
+
+       if name.startswith("user/"):
+           return False
+
+       if name in ["blankgroup",
+                   "pl_sirius",
+                   "princeton_codnsdemux", "princeton_comon", "princeton_coredirect", "princeton_slicestat", "princeton_vcoblitz"]:
+           return False
+
+       return True
+
+   def mail_invoices(self, filter={}, email_list=None):
+        what_i_did = {}
+        what_i_did["_filter"] = filter
+
+        accounts = self.accounts.get_accounts(filter)
+        for account in accounts:
+            if not self.is_billable_account(account.name):
+                continue
+
+            if not account.billingContacts:
+                account.billingContacts = ", ".join(self.get_default_billing_emails(account.name))
+                account.commit()
+
+            if account.billingContacts:
+                if email_list == None:
+                    this_email_list = [x.strip() for x in account.billingContacts.split(",")]
+                else:
+                    this_email_list = email_list
+
+                if (self.mail_invoice(account, email_list = this_email_list)):
+                    what_i_did[account.name] = email_list
+
+        return what_i_did
+
    def do_nightly(self):
         self.mylogger().info("doing do_nightly")