Repository: softlayer/softlayer-python
Pull request #458 (2014-11-24 21:44:38)

Adding billing
~/office/softlayer-python (billing) > sl billing --help
Usage: sl billing [OPTIONS] COMMAND [ARGS]...

  Billing.

Options:
  --help  Show this message and exit.

Commands:
  info     List billing details of Account.
  list     List billing information for orders.
  summary  List billing summary.


(buzzenv)~/office/softlayer-python (billing) > sl billing info 

:...........................:....................................:
:            Name           :               Value                :
:...........................:....................................:
:             id            :               153465                :
:        paymentTerms       :                                    :
:         modifyDate        :     2014-03-12T09:40:50-07:00      :
:   anniversaryDayOfMonth   :                 9                  :
: lastFourPaymentCardDigits :                                    :
:    cardExpirationMonth    :                                    :
:     cardExpirationYear    :                                    :
:   percentDiscountOnetime  :                100                 :
:      sparePoolAmount      :                 0                  :
:      lastPaymentDate      :                                    :
:         createDate        :     2010-09-09T11:53:45-07:00      :
:  percentDiscountRecurring :                100                 :
:          currency         : :.........:......................: :
:                           : :   Name  :        Value         : :
:                           : :.........:......................: :
:                           : : keyName :         USD          : :
:                           : :    id   :         556          : :
:                           : :   name  : United States dollar : :
:                           : :.........:......................: :
:...........................:....................................:


(buzzenv)~/office/softlayer-python (billing) > sl billing list --help
Usage: sl billing list [OPTIONS]

  List billing information for orders.

Options:
  -f, --start_date TEXT  cost incurred from from_date
  -e, --end_date TEXT    end date to consider, default is latest time stamp
  --help                 Show this message and exit.



(buzzenv)~/office/softlayer-python (billing) > sl billing list -f 2014-02-06  -e 2014-04-01
:..........:...............:...................:......:...........................:
: Order ID : Resource Name :   Resource Type   : cost :        create_date        :
:..........:...............:...................:......:...........................:
: 1863254  :     njain2    : 2 x 2.0 GHz Cores : 0.0  : 2014-03-24T14:13:28-07:00 :
:..........:...............:...................:......:...........................:



~/office/softlayer-python (billing) > sl billing summary
:........................:...........................:
:          Name          :           Value           :
:........................:...........................:
:    Current Balance     :            1615           :
: Estimated Next Balance :            3.04           :
:   Last Billing Date    : 2014-11-09T00:00:00-06:00 :
:........................:...........................:





Patch:
diff --git a/SoftLayer/CLI/billing/__init__.py b/SoftLayer/CLI/billing/__init__.py
new file mode 100644
index 00000000..ce714135
--- /dev/null
+++ b/SoftLayer/CLI/billing/__init__.py
@@ -0,0 +1,2 @@
+"""Billing."""
+# :license: MIT, see LICENSE for more details.
diff --git a/SoftLayer/CLI/billing/info.py b/SoftLayer/CLI/billing/info.py
new file mode 100644
index 00000000..02b7d9b3
--- /dev/null
+++ b/SoftLayer/CLI/billing/info.py
@@ -0,0 +1,39 @@
+"""List billing details of Account."""
+# :license: MIT, see LICENSE for more details.
+
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+
+import click
+
+
+@click.command()
+@environment.pass_env
+def cli(env):
+    """List billing details of Account."""
+    billing = SoftLayer.BillingManager(env.client)
+    table = formatting.Table(['Name', 'Value'])
+    info = billing.get_info()
+    table.add_row(['accountId', info['accountId']])
+    table.add_row(['id', info['id']])
+    table.add_row(['paymentTerms', info['paymentTerms']])
+    table.add_row(['modifyDate', info['modifyDate']])
+    table.add_row(['anniversaryDayOfMonth', info['anniversaryDayOfMonth']])
+    table.add_row(['lastFourPaymentCardDigits',
+                   info['lastFourPaymentCardDigits']])
+    table.add_row(['cardExpirationMonth', info['cardExpirationMonth']])
+    table.add_row(['cardExpirationYear', info['cardExpirationYear']])
+    table.add_row(['percentDiscountOnetime', info['percentDiscountOnetime']])
+    table.add_row(['sparePoolAmount', info['sparePoolAmount']])
+    table.add_row(['lastPaymentDate', info['lastPaymentDate']])
+    table.add_row(['createDate', info['createDate']])
+    table.add_row(['percentDiscountRecurring',
+                   info['percentDiscountRecurring']])
+    pass_table = formatting.Table(['Name', 'Value'])
+    result = info['currency']
+    for key in result.keys():
+        pass_table.add_row([key, result[key]])
+    table.add_row(['currency', pass_table])
+    return table
diff --git a/SoftLayer/CLI/billing/list.py b/SoftLayer/CLI/billing/list.py
new file mode 100644
index 00000000..1a245509
--- /dev/null
+++ b/SoftLayer/CLI/billing/list.py
@@ -0,0 +1,38 @@
+"""List billing information of orders."""
+# :license: MIT, see LICENSE for more details.
+
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+from SoftLayer import utils
+
+import click
+
+
+@click.command()
+@click.option('--start_date', '-f', help='cost incurred from this start date')
+@click.option('--end_date', '-e',
+              help='cost incurred before this end date'
+                   ' (default is current time stamp)')
+@environment.pass_env
+def cli(env, start_date, end_date):
+    """List billing information for orders."""
+    billing = SoftLayer.BillingManager(env.client)
+    from_date = start_date
+    to_date = end_date
+    table = formatting.Table(['Order ID', 'Resource Name', 'Resource Type',
+                              'cost', 'create_date'])
+    resources = billing.list_resources(from_date, to_date)
+
+    for resource in resources:
+        resource = utils.NestedDict(resource)
+        table.add_row([
+            resource['id'],
+            resource['hostName'],
+            resource['resourceType'],
+            resource['cost'],
+            resource['createDate']
+        ])
+
+    return table
diff --git a/SoftLayer/CLI/billing/summary.py b/SoftLayer/CLI/billing/summary.py
new file mode 100644
index 00000000..2a873aa2
--- /dev/null
+++ b/SoftLayer/CLI/billing/summary.py
@@ -0,0 +1,25 @@
+"""List billing summary."""
+# :license: MIT, see LICENSE for more details.
+
+
+import SoftLayer
+from SoftLayer.CLI import environment
+from SoftLayer.CLI import formatting
+
+import click
+
+
+@click.command()
+@environment.pass_env
+def cli(env):
+    """List billing summary."""
+    billing = SoftLayer.BillingManager(env.client)
+    table = formatting.Table(['Name', 'Value'])
+    result = billing.get_summary()
+    balance = result['balance']
+    next_balance = result['nextbalance']
+    last_bill_date = result['lastbilldate']
+    table.add_row(['Current Balance', balance])
+    table.add_row(['Estimated Next Balance', next_balance])
+    table.add_row(['Last Billing Date', last_bill_date])
+    return table
diff --git a/SoftLayer/CLI/routes.py b/SoftLayer/CLI/routes.py
index 25d116d2..a64b1200 100644
--- a/SoftLayer/CLI/routes.py
+++ b/SoftLayer/CLI/routes.py
@@ -7,6 +7,11 @@
 """
 
 ALL_ROUTES = [
+    ('billing', 'SoftLayer.CLI.billing'),
+    ('billing:list', 'SoftLayer.CLI.billing.list:cli'),
+    ('billing:info', 'SoftLayer.CLI.billing.info:cli'),
+    ('billing:summary', 'SoftLayer.CLI.billing.summary:cli'),
+
     ('call-api', 'SoftLayer.CLI.call_api:cli'),
 
     ('vs', 'SoftLayer.CLI.virt'),
diff --git a/SoftLayer/consts.py b/SoftLayer/consts.py
index 3c2bbcda..dc1c8c38 100644
--- a/SoftLayer/consts.py
+++ b/SoftLayer/consts.py
@@ -10,4 +10,4 @@
 API_PRIVATE_ENDPOINT = 'https://api.service.softlayer.com/xmlrpc/v3.1/'
 API_PUBLIC_ENDPOINT_REST = 'https://api.softlayer.com/rest/v3.1/'
 API_PRIVATE_ENDPOINT_REST = 'https://api.service.softlayer.com/rest/v3.1/'
-USER_AGENT = "softlayer-python/%s" % VERSION
+USER_AGENT = "SoftLayer Python %s" % VERSION
diff --git a/SoftLayer/managers/__init__.py b/SoftLayer/managers/__init__.py
index fbcb427d..4acfc325 100644
--- a/SoftLayer/managers/__init__.py
+++ b/SoftLayer/managers/__init__.py
@@ -8,7 +8,7 @@
     :license: MIT, see LICENSE for more details.
 """
 # pylint: disable=w0401
-
+from SoftLayer.managers.billing import BillingManager  # NOQA
 from SoftLayer.managers.cdn import CDNManager  # NOQA
 from SoftLayer.managers.dns import DNSManager  # NOQA
 from SoftLayer.managers.firewall import FirewallManager  # NOQA
@@ -25,8 +25,9 @@
 from SoftLayer.managers.ticket import TicketManager  # NOQA
 from SoftLayer.managers.vs import VSManager  # NOQA
 
-__all__ = ['DNSManager', 'FirewallManager', 'HardwareManager',
-           'ImageManager', 'MessagingManager', 'MetadataManager', 'CDNManager',
-           'NetworkManager', 'SshKeyManager', 'SSLManager', 'TicketManager',
+__all__ = ['BillingManager', 'DNSManager', 'FirewallManager',
+           'HardwareManager', 'ImageManager', 'MessagingManager',
+           'MetadataManager', 'CDNManager', 'NetworkManager',
+           'SshKeyManager', 'SSLManager', 'TicketManager',
            'VSManager', 'ISCSIManager', 'LoadBalancerManager',
            'OrderingManager']
diff --git a/SoftLayer/managers/billing.py b/SoftLayer/managers/billing.py
new file mode 100644
index 00000000..95e13b30
--- /dev/null
+++ b/SoftLayer/managers/billing.py
@@ -0,0 +1,232 @@
+"""
+    SoftLayer.billing
+    ~~~~~~~~~~~~
+    Billing Manager/helpers
+
+    :license: MIT, see LICENSE for more details.
+"""
+import calendar
+import datetime
+
+from SoftLayer import utils
+
+import math
+import time
+
+
+class BillingManager(object):
+    """Manages Billing details.
+
+    :param SoftLayer.managers.orderingManager
+    """
+
+    def __init__(self, client):
+        """init
+
+        :param client
+        :return: billing account info
+        """
+        self.client = client
+        self.account = self.client['Account']
+        self.billing_order = self.client['Billing_Order']
+
+    def get_info(self):
+        """get info
+
+        :return: billing account info
+        """
+        result = self.account.getBillingInfo()
+        return result
+
+    def get_balance(self):
+        """get balance
+
+        :return: billing account info
+        """
+        result = self.account.getBalance()
+        return result
+
+    def get_next_balance(self):
+        """get next balance
+
+        :return: billing account info
+        """
+        result = self.account.getNextInvoiceTotalAmount()
+        return result
+
+    def get_latest_bill_date(self):
+        """get latest balance
+
+        :return: billing account info
+        """
+        result = self.account.getLatestBillDate()
+        return result
+
+    def get_summary(self):
+        """get summary
+
+        :return: billing account info
+        """
+        result = {
+            'lastbilldate': self.get_latest_bill_date(),
+            'balance': self.get_balance(),
+            'nextbalance': self.get_next_balance()
+        }
+        return result
+
+    def get_next_bill_items(self):
+        """get next bill items
+
+        :return: billing account info
+        """
+        result = self.account.getAllBillingItems()
+        return result
+
+    def list_resources(self, from_date=None, to_date=None, **kwargs):
+        """Retrieve a list of all ordered resources along with their costing.
+
+        :param dict \\*\\*kwargs: response-level option (limit)
+        :returns: Returns a list of dictionaries representing the
+        matching ordered resorces by a user along with their costing
+        """
+        params = {}
+        if 'mask' not in kwargs:
+            items = set([
+                'order[items[description,billingItem[id,hostName,'
+                'hourlyRecurringFee,cancellationDate,createDate,'
+                'laborFee,oneTimeFee,setupFee]]]'
+            ])
+            params['mask'] = "mask[%s]" % ','.join(items)
+
+        _filter = utils.NestedDict({})
+        user = self.account.getCurrentUser(mask='mask[id]')
+        _filter['orders']['userRecordId'] = utils.query_filter(user['id'])
+        date_format = '%Y-%m-%d'
+
+        if from_date and to_date:
+            _filter['orders']['createDate'] = utils.query_filter_date(
+                from_date, to_date)
+        elif from_date:
+            from_date_filter = '>=' + ' ' + from_date
+            _filter['orders']['createDate'] = utils.query_filter(
+                from_date_filter)
+        elif to_date:
+            to_date_filter = '<=' + ' ' + to_date
+            _filter['orders']['createDate'] = utils.query_filter(
+                to_date_filter)
+        orders = self.account.getOrders(filter=_filter.to_dict())
+        result = []
+
+        for order in orders:
+            billing_order = self.client['Billing_Order']
+            params['id'] = order_id = order['id']
+            items = billing_order.getItems(**params)
+            cost = float(0.0)
+            resource_type = ''
+            flag = 1
+            hostname = ''
+            creation_date = ''
+
+            for item in items:
+                if flag == 1:
+                    resource_type = item['description']
+
+                if 'Core' in resource_type and flag == 1:
+                    hostname = item['billingItem']['hostName']
+                flag = 0
+                usedmonths = 1
+
+                creation_date = item['billingItem']['createDate']
+                cancellation_date = item['billingItem']['cancellationDate']
+
+                if 'hourlyRecurringFee' not in item['billingItem']:
+                    create_date = datetime.datetime.strptime(
+                        format_date(
+                            item['billingItem']['createDate']), date_format)
+                    if cancellation_date:
+                        cancel_date = datetime.datetime.strptime(
+                            format_date(
+                                item['billingItem']['cancellationDate']),
+                            date_format)
+                        usedmonths = get_month_delta(create_date, cancel_date)
+                    else:
+                        now = datetime.datetime.strptime(
+                            format_date(str(datetime.datetime.now())),
+                            date_format)
+                        usedmonths = get_month_delta(create_date, now)
+
+                    usedmonths += 1
+
+                    cost += float(
+                        billing_order.getOrderTotalOneTime(
+                            id=order['id']
+                        ) + billing_order.getOrderTotalRecurring(
+                            id=order['id']
+                        ) * usedmonths
+                    )
+
+                elif not cancellation_date:
+                    virtual_guest = self.account.getHourlyVirtualGuests(
+                        filter={
+                            'hourlyVirtualGuests': {
+                                'hourlyVirtualGuests': {
+                                    'billingItem': {
+                                        'id': {
+                                            'operation':
+                                                item['billingItem']['id']}}}}})
+                    if virtual_guest:
+                        guest = virtual_guest[0]
+                        cost = guest['billingItem']['currentHourlyCharge']
+
+                else:
+                    create_date = datetime.datetime.strptime(
+                        format_date(creation_date), date_format)
+                    cancel_date = datetime.datetime.strptime(
+                        format_date(cancellation_date), date_format)
+                    d1_ts = time.mktime(create_date.timetuple())
+                    d2_ts = time.mktime(cancel_date.timetuple())
+                    usedhours = math.ceil(d2_ts - d1_ts)/3600
+                    cost += usedhours * float(
+                        item['billingItem']['hourlyRecurringFee']
+                    ) + float(
+                        item['billingItem']['laborFee']
+                    ) + float(
+                        item['billingItem']['oneTimeFee']
+                    ) + float(
+                        item['billingItem']['setupFee'])
+            resource = {
+                'id': order_id,
+                'resourceType': resource_type,
+                'hostName': hostname,
+                'createDate': creation_date,
+                'cost': cost
+            }
+            result.append(resource)
+        return result
+
+
+def format_date(date):
+    """format  date.
+
+    :param date: YY-MM-DD
+    """
+    result = date.replace('T', ' ')
+    return result[0:10]
+
+
+def get_month_delta(date1, date2):
+    """get month
+
+    :param date1: YY-MM-DD
+    :param date2: YY-MM-DD
+    :return: delta
+    """
+    delta = 0
+    while True:
+        mdays = calendar.monthrange(date1.year, date1.month)[1]
+        date1 += datetime.timedelta(days=mdays)
+        if date1 <= date2:
+            delta += 1
+        else:
+            break
+    return delta
diff --git a/SoftLayer/utils.py b/SoftLayer/utils.py
index e3d0f952..7443c3c5 100644
--- a/SoftLayer/utils.py
+++ b/SoftLayer/utils.py
@@ -5,6 +5,7 @@
 
     :license: MIT, see LICENSE for more details.
 """
+import datetime
 import re
 
 import six
@@ -112,6 +113,25 @@ def query_filter(query):
     return {'operation': query}
 
 
+def query_filter_date(start, end):
+    """Query filters given start and end date.
+
+    :param start:YY-MM-DD
+    :param end: YY-MM-DD
+    """
+    sdate = datetime.datetime.strptime(start, "%Y-%m-%d")
+    edate = datetime.datetime.strptime(end, "%Y-%m-%d")
+    startdate = "%s/%s/%s" % (sdate.month, sdate.day, sdate.year)
+    enddate = "%s/%s/%s" % (edate.month, edate.day, edate.year)
+    return {
+        'operation': 'betweenDate',
+        'options': [
+            {'name': 'startDate', 'value': [startdate+' 0:0:0']},
+            {'name': 'endDate', 'value': [enddate+' 0:0:0']}
+        ]
+    }
+
+
 class IdentifierMixin(object):
     """Mixin used to resolve ids from other names of objects.
 


Test patch:
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Account.py b/SoftLayer/testing/fixtures/SoftLayer_Account.py
index dab7ff6c..3701cbd7 100644
--- a/SoftLayer/testing/fixtures/SoftLayer_Account.py
+++ b/SoftLayer/testing/fixtures/SoftLayer_Account.py
@@ -459,3 +459,35 @@
     'name': 'TestQuote1234',
     'quoteKey': '1234test4321',
 }]
+
+
+getOrders = [{
+    'id': 1234,
+    'resourceType': '1 x 2.0 GHz Core',
+    'hostName': 'test',
+    'createDate': '2014-05-01T14:03:04-07:00',
+    'cost': 0.0
+}]
+
+getBillingInfo = [{
+    'id': 1234,
+    'accountId': 123,
+    'resourceType': '1 x 2.0 GHz Core',
+    'hostName': 'test',
+    'modifyDate': '2014-05-01T14:03:04-07:00',
+    'createDate': '2014-05-01T14:03:04-07:00',
+    'anniversaryDayOfMonth': 2,
+    'percentDiscountOnetime': 2,
+    'sparePoolAmount': 0,
+    'currency': {
+        'KeyName': 'usd',
+        'id': 1,
+        'name': 'dollars'
+    }
+}]
+
+getLatestBillDate = '2014-05-01T14:03:04-07:00'
+
+getBalance = 40
+
+getNextInvoiceTotalAmount = 2
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Billing_Order.py b/SoftLayer/testing/fixtures/SoftLayer_Billing_Order.py
new file mode 100644
index 00000000..ab41cd24
--- /dev/null
+++ b/SoftLayer/testing/fixtures/SoftLayer_Billing_Order.py
@@ -0,0 +1,14 @@
+getItems = [{
+    'description': '1 x 2.0 GHz Core',
+    'billingItem': {
+        'setupFee': '0',
+        'createDate': '2014-11-12T14:54:05-06:00',
+        'oneTimeFee': '0', 'cancellationDate': '',
+        'id': 1234,
+        'hostName': 'testhost',
+        'laborFee': '0'
+    }
+}]
+
+getOrderTotalOneTime = 0.0
+getOrderTotalRecurring = 0.0
diff --git a/SoftLayer/tests/managers/billing_tests.py b/SoftLayer/tests/managers/billing_tests.py
new file mode 100644
index 00000000..afc1e6ac
--- /dev/null
+++ b/SoftLayer/tests/managers/billing_tests.py
@@ -0,0 +1,42 @@
+"""
+    SoftLayer.tests.managers.ticket_tests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :license: MIT, see LICENSE for more details.
+"""
+import datetime
+
+import SoftLayer
+from SoftLayer import testing
+
+
+class BillingTests(testing.TestCase):
+
+    def set_up(self):
+        self.billing = SoftLayer.BillingManager(self.client)
+
+    def test_list_billing_items(self):
+        self.billing.list_resources()
+        self.assert_called_with('SoftLayer_Account', 'getOrders')
+
+    def test_billing_info(self):
+        self.billing.get_info()
+        self.assert_called_with('SoftLayer_Account', 'getBillingInfo')
+
+    def test_billing_summary(self):
+        self.billing.get_summary()
+        self.assert_called_with('SoftLayer_Account',
+                                'getNextInvoiceTotalAmount')
+        self.assert_called_with('SoftLayer_Account', 'getLatestBillDate')
+        self.assert_called_with('SoftLayer_Account', 'getBalance')
+
+    def test_billing_summary_with_date(self):
+        results = self.billing.list_resources(from_date='2014-02-06')
+        now = datetime.date.today()
+        now_delta = now - datetime.date(2014, 2, 6)
+        for result in results:
+            create_date = result['createDate']
+            strp_date = create_date[0:10]
+            row_date = datetime.datetime.strptime(strp_date, '%Y-%m-%d')
+            row_delta = now - row_date.date()
+            self.assertTrue(now_delta >= row_delta)