| 1 | # Copyright (C) 2006 Rural Link Ltd. |
|---|
| 2 | # |
|---|
| 3 | # This file is an extension to crcnetd - CRCnet Configuration System Daemon |
|---|
| 4 | # |
|---|
| 5 | # RuralLink Billing Module - Provides logic for manipulating customer accounts |
|---|
| 6 | # according to the Rural Link business rules. |
|---|
| 7 | # |
|---|
| 8 | # Author: Matt Brown <matt@crc.net.nz> |
|---|
| 9 | # Ivan Meredith <ivan@ivan.net.nz> |
|---|
| 10 | # Version: $Id$ |
|---|
| 11 | # |
|---|
| 12 | # No usage or redistribution rights are granted for this file unless |
|---|
| 13 | # otherwise confirmed in writing by the copyright holder. |
|---|
| 14 | from crcnetd._utils.ccsd_common import * |
|---|
| 15 | from crcnetd._utils.ccsd_log import * |
|---|
| 16 | from crcnetd._utils.ccsd_events import * |
|---|
| 17 | from crcnetd._utils.ccsd_session import getSession, getSessionE |
|---|
| 18 | from crcnetd._utils.ccsd_server import exportViaXMLRPC, registerResource, \ |
|---|
| 19 | registerRecurring, registerHalfHourly, registerDailyAt |
|---|
| 20 | from crcnetd._utils.ccsd_ca import ccs_ca, getCertificateParameters, \ |
|---|
| 21 | CERT_PARAM_NAMES, REVOKE_SUPERSEDED |
|---|
| 22 | from datetime import * |
|---|
| 23 | ccs_mod_type = CCSD_SERVER |
|---|
| 24 | |
|---|
| 25 | def updateRadius(): |
|---|
| 26 | """ |
|---|
| 27 | Should this function be here or just consolidate into checkUsage(), ill figure |
|---|
| 28 | out later, for now this is fine. |
|---|
| 29 | """ |
|---|
| 30 | session = getSession(ADMIN_SESSION_ID) |
|---|
| 31 | |
|---|
| 32 | sql = "SELECT FROM update_customer_state()" |
|---|
| 33 | |
|---|
| 34 | session.execute(sql, ()) |
|---|
| 35 | |
|---|
| 36 | def checkUsage(): |
|---|
| 37 | """ |
|---|
| 38 | Checks each customers usage, and if usage is greater than the cap, |
|---|
| 39 | do the cap action |
|---|
| 40 | """ |
|---|
| 41 | #Update the customer_states before checking data used. |
|---|
| 42 | updateRadius() |
|---|
| 43 | |
|---|
| 44 | session = getSession(ADMIN_SESSION_ID) |
|---|
| 45 | |
|---|
| 46 | sql = "SELECT DISTINCT * FROM customer_state WHERE used_mb > cap_at_mb" |
|---|
| 47 | |
|---|
| 48 | res = session.query(sql, ()) |
|---|
| 49 | for state in res: |
|---|
| 50 | doCapAction(state) |
|---|
| 51 | |
|---|
| 52 | @registerHalfHourly() |
|---|
| 53 | def updateStatus(): |
|---|
| 54 | """ |
|---|
| 55 | This is the function that is run every 30 mins to update the state |
|---|
| 56 | table with the data done for the last 30mins. |
|---|
| 57 | """ |
|---|
| 58 | checkUsage() |
|---|
| 59 | |
|---|
| 60 | def doRollover(type): |
|---|
| 61 | """ |
|---|
| 62 | Performs the nightly rollover check. |
|---|
| 63 | """ |
|---|
| 64 | |
|---|
| 65 | session = getSession(ADMIN_SESSION_ID) |
|---|
| 66 | |
|---|
| 67 | sql = "SELECT * FROM rollover WHERE cap_period=%s" |
|---|
| 68 | |
|---|
| 69 | res = session.query(sql, (type)) |
|---|
| 70 | |
|---|
| 71 | for ro in res: |
|---|
| 72 | #Generate the billing records |
|---|
| 73 | sql = "INSERT INTO billing_record(contact_id, record_date, " \ |
|---|
| 74 | "description, amount, quantity) VALUES (%s,CURRENT_TIMESTAMP, " \ |
|---|
| 75 | "%s, %s,1);" |
|---|
| 76 | |
|---|
| 77 | session.execute(sql, (ro["contact_id"],ro["plan_name"], ro["price"])) |
|---|
| 78 | |
|---|
| 79 | #Update the customer state. |
|---|
| 80 | sql = "UPDATE customer_state SET plan_start=CURRENT_TIMESTAMP, "\ |
|---|
| 81 | "cap_at_mb=%s, used_mb=0, radius_plan=%s WHERE contact_id=%s" |
|---|
| 82 | |
|---|
| 83 | session.execute(sql, (ro["included_mb"], ro["contact_id"], \ |
|---|
| 84 | ro["radius_plan"])) |
|---|
| 85 | |
|---|
| 86 | @registerDailyAt((00,30,00)) |
|---|
| 87 | def updateNightly(): |
|---|
| 88 | """ |
|---|
| 89 | This function is called every night and does checks do see if caps need |
|---|
| 90 | rolling over etc. |
|---|
| 91 | """ |
|---|
| 92 | #If its the start of the month, its time to bill them for a new |
|---|
| 93 | #month |
|---|
| 94 | if datetime.now().day == 1: |
|---|
| 95 | doRollover("monthy") |
|---|
| 96 | #Monday = 0 ... Sunday = 6 |
|---|
| 97 | if datetime.now().weekday() == 1: |
|---|
| 98 | doRollover("weekly") |
|---|
| 99 | |
|---|
| 100 | |
|---|
| 101 | #do all daily rollovers |
|---|
| 102 | doRollover("daily") |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | def doCapAction(customer): |
|---|
| 106 | """ |
|---|
| 107 | Supported actions at the moment are: |
|---|
| 108 | Throttling. |
|---|
| 109 | Top up. |
|---|
| 110 | """ |
|---|
| 111 | session = getSession(ADMIN_SESSION_ID) |
|---|
| 112 | |
|---|
| 113 | sql = "SELECT cap_action, cap_param, cap_price FROM billing_plan" \ |
|---|
| 114 | " WHERE plan_id=%s" |
|---|
| 115 | |
|---|
| 116 | res = session.query(sql, (customer["plan_id"])) |
|---|
| 117 | |
|---|
| 118 | if res[0]["cap_action"] == "throttle": |
|---|
| 119 | sql = "UPDATE customer_state SET radius_plan=%s WHERE contact_id=%s" |
|---|
| 120 | session.execute(sql,(res[0]["cap_param"], customer["contact_id"])) |
|---|
| 121 | elif res[0]["cap_action"] == "purchase": |
|---|
| 122 | cap_at_mb = customer["cap_at_mb"] |
|---|
| 123 | used_mb = customer["used_mb"] |
|---|
| 124 | |
|---|
| 125 | # If for some reason they manage to go over the cap + 1 unit of |
|---|
| 126 | # cap_param we want to keep selling them data until the cap is |
|---|
| 127 | # higher than there current data. |
|---|
| 128 | while cap_at_mb < used_mb: |
|---|
| 129 | sql = "UPDATE customer_state SET cap_at_mb=cap_at_mb+%s " \ |
|---|
| 130 | "WHERE contact_id=%s" |
|---|
| 131 | session.execute(sql,(res[0]["cap_param"], customer["contact_id"])) |
|---|
| 132 | |
|---|
| 133 | sql = "INSERT INTO billing_record (contact_id, record_date, " \ |
|---|
| 134 | "description, amount, quantity) VALUES (%%s, CURRENT_TIMESTAMP, " \ |
|---|
| 135 | "'%sGB data pack', %%s, 1)" % (res[0]["cap_param"]/1024) |
|---|
| 136 | session.execute(sql, (customer["contact_id"], res[0]["cap_price"])) |
|---|
| 137 | cap_at_mb += res[0]["cap_param"] |
|---|
| 138 | |
|---|
| 139 | def generateInvoice(contact_id): |
|---|
| 140 | """ |
|---|
| 141 | Generates an invoice for all bills in the database that havent been |
|---|
| 142 | assigned an invoice number. |
|---|
| 143 | """ |
|---|
| 144 | |
|---|
| 145 | def emailInvoiceTo(contact_id, invoice_id): |
|---|
| 146 | """ |
|---|
| 147 | Emails an invoice to a customer. |
|---|
| 148 | """ |
|---|