Repository: softlayer/softlayer-python
Pull request #1119 (2019-03-20 20:40:45)

Fix order place quantity option
Fix order place quantity option https://github.com/softlayer/softlayer-python/issues/1116.

**>slcli order place --help**

```
Options:
  --preset TEXT                         The order preset (if required by the package)
  --verify                              Flag denoting whether or not to only verify the order, not place it
  --quantity INTEGER                    The quantity of the item being ordered
  --billing [hourly|monthly]            Billing rate  [default: hourly]
  --complex-type TEXT                   The complex type of the order. This typically begins with                                                       'SoftLayer_Container_Product_Order_'.
  --extras TEXT                         JSON string denoting extra data that needs to be sent with the order
  -h, --help                            Show this message and exit.
```

**Example1:**
`>slcli order place --quantity 2 --billing monthly VIRTUAL_ROUTER_APPLIANCE_1_GPBS DALLAS05 INTEL_INTEL_XEON_E31270_V6_3_80 OS_JUNIPER_VSRX_15_X_UP_TO_1GBPS_STANDARD RAM_32_GB_DDR4_2133_ECC_NON_REG DISK_CONTROLLER_RAID HARD_DRIVE_3_00TB_SATA_III SRIOV_ENABLED BANDWIDTH_5000_GB MONITORING_HOST_PING 1_GBPS_REDUNDANT_PUBLIC_PRIVATE_NETWORK_UPLINKS REBOOT_KVM_OVER_IP 1_IP_ADDRESS NOTIFICATION_EMAIL_AND_TICKET AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING --complex-type SoftLayer_Container_Product_Order_Hardware_Server_Gateway_Appliance --extras '{"hardware":[{"hostname":"gateway01","domain":"example.com"},{"hostname":"gateway02","domain":"example.com"}]}'`

**Output:**
:...................................................................................:
: name : value :
:...................................................................................:
: id : 35311111 :
: created : 2019-03-19T10:04:09-06:00 :
: status : PENDING_AUTO_APPROVAL :
:....................................................................................

**Example2:
By default the quantity is 1:**
`>slcli order place --billing monthly VIRTUAL_ROUTER_APPLIANCE_1_GPBS DALLAS05 INTEL_INTEL_XEON_E31270_V6_3_80 OS_JUNIPER_VSRX_15_X_UP_TO_1GBPS_STANDARD RAM_32_GB_DDR4_2133_ECC_NON_REG DISK_CONTROLLER_RAID HARD_DRIVE_3_00TB_SATA_III SRIOV_ENABLED BANDWIDTH_5000_GB MONITORING_HOST_PING 1_GBPS_REDUNDANT_PUBLIC_PRIVATE_NETWORK_UPLINKS REBOOT_KVM_OVER_IP 1_IP_ADDRESS NOTIFICATION_EMAIL_AND_TICKET AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING --complex-type SoftLayer_Container_Product_Order_Hardware_Server_Gateway_Appliance --extras '{"hardware":[{"hostname":"gateway01","domain":"example.com"}]}'`

**Output:**
:...................................................................................:
: name : value :
:...................................................................................:
: id : 35322222 :
: created : 2019-03-19T11:08:14-06:00 :
: status : PENDING_AUTO_APPROVAL :
:....................................................................................

**Example3:
ORDER CLOUD_SERVER:**
`>slcli order place --quantity 2 --billing hourly CLOUD_SERVER AMSTERDAM GUEST_PRIVATE_CORE_1 RAM_2_GB OS_CENTOS_7_X_MINIMAL_64_BIT GUEST_DISK_25_GB_SAN BANDWIDTH_0_GB_2 1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS MONITORING_HOST_PING AUTOMATED_NOTIFICATION UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT NESSUS_VULNERABILITY_ASSESSMENT_REPORTING 1_IP_ADDRESS NOTIFICATION_EMAIL_AND_TICKET REBOOT_REMOTE_CONSOLE --extras "{"privateCloudOrderFlag":false,"useHourlyPricing":true,""virtualGuests": [{"hostname": "test", "domain": "softlayer.com"},{"hostname": "test1", "domain": "softlayer.com"}]}" --complex-type SoftLayer_Container_Product_Order_Virtual_Guest`

**Output:**
:...................................................................................:
: name : value :
:...................................................................................:
: id : 35333333 :
: created : 2019-03-19T11:15:28-06:00 :
: status : PENDING_AUTO_APPROVAL :
:....................................................................................


Patch:
diff --git a/SoftLayer/CLI/order/place.py b/SoftLayer/CLI/order/place.py
index 3fe13caa..c6f6a129 100644
--- a/SoftLayer/CLI/order/place.py
+++ b/SoftLayer/CLI/order/place.py
@@ -23,6 +23,10 @@
 @click.option('--verify',
               is_flag=True,
               help="Flag denoting whether or not to only verify the order, not place it")
+@click.option('--quantity',
+              type=int,
+              default=1,
+              help="The quantity of the item being ordered")
 @click.option('--billing',
               type=click.Choice(['hourly', 'monthly']),
               default='hourly',
@@ -35,7 +39,7 @@
 @click.argument('order_items', nargs=-1)
 @environment.pass_env
 def cli(env, package_keyname, location, preset, verify, billing, complex_type,
-        extras, order_items):
+        quantity, extras, order_items):
     """Place or verify an order.
 
     This CLI command is used for placing/verifying an order of the specified package in
@@ -84,7 +88,7 @@ def cli(env, package_keyname, location, preset, verify, billing, complex_type,
     args = (package_keyname, location, order_items)
     kwargs = {'preset_keyname': preset,
               'extras': extras,
-              'quantity': 1,
+              'quantity': quantity,
               'complex_type': complex_type,
               'hourly': bool(billing == 'hourly')}
 
diff --git a/SoftLayer/managers/ordering.py b/SoftLayer/managers/ordering.py
index fbc56b65..c82a7ab5 100644
--- a/SoftLayer/managers/ordering.py
+++ b/SoftLayer/managers/ordering.py
@@ -545,8 +545,8 @@ def generate_order(self, package_keyname, location, item_keynames, complex_type=
         #                                    'domain': 'softlayer.com'}]}
         order.update(extras)
         order['packageId'] = package['id']
-        order['location'] = self.get_location_id(location)
         order['quantity'] = quantity
+        order['location'] = self.get_location_id(location)
         order['useHourlyPricing'] = hourly
 
         preset_core = None


Test patch:
diff --git a/tests/CLI/modules/order_tests.py b/tests/CLI/modules/order_tests.py
index 854690ac..02141e80 100644
--- a/tests/CLI/modules/order_tests.py
+++ b/tests/CLI/modules/order_tests.py
@@ -104,6 +104,27 @@ def test_place(self):
                           'status': 'APPROVED'},
                          json.loads(result.output))
 
+    def test_place_with_quantity(self):
+        order_date = '2017-04-04 07:39:20'
+        order = {'orderId': 1234, 'orderDate': order_date, 'placedOrder': {'status': 'APPROVED'}}
+        verify_mock = self.set_mock('SoftLayer_Product_Order', 'verifyOrder')
+        place_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder')
+        items_mock = self.set_mock('SoftLayer_Product_Package', 'getItems')
+
+        verify_mock.return_value = self._get_verified_order_return()
+        place_mock.return_value = order
+        items_mock.return_value = self._get_order_items()
+
+        result = self.run_command(['-y', 'order', 'place', '--quantity=2', 'package', 'DALLAS13', 'ITEM1',
+                                   '--complex-type', 'SoftLayer_Container_Product_Order_Thing'])
+
+        self.assert_no_fail(result)
+        self.assert_called_with('SoftLayer_Product_Order', 'placeOrder')
+        self.assertEqual({'id': 1234,
+                          'created': order_date,
+                          'status': 'APPROVED'},
+                         json.loads(result.output))
+
     def test_place_extras_parameter_fail(self):
         result = self.run_command(['-y', 'order', 'place', 'package', 'DALLAS13', 'ITEM1',
                                    '--extras', '{"device":['])
diff --git a/tests/managers/ordering_tests.py b/tests/managers/ordering_tests.py
index b5d2aaa4..66eb2f40 100644
--- a/tests/managers/ordering_tests.py
+++ b/tests/managers/ordering_tests.py
@@ -370,6 +370,36 @@ def test_generate_order_with_preset(self):
         mock_get_ids.assert_called_once_with(pkg, items, 8)
         self.assertEqual(expected_order, order)
 
+    def test_generate_order_with_quantity(self):
+        pkg = 'PACKAGE_KEYNAME'
+        quantity = 2
+        items = ['ITEM1', 'ITEM2']
+        extras = {"hardware": [{"hostname": "test01", "domain": "example.com"},
+                               {"hostname": "test02", "domain": "example.com"}]}
+        complex_type = 'My_Type'
+        expected_order = {'orderContainers': [
+            {'complexType': 'My_Type',
+             'hardware': [{'domain': 'example.com',
+                           'hostname': 'test01'},
+                          {'domain': 'example.com',
+                           'hostname': 'test02'}],
+             'location': 1854895,
+             'packageId': 1234,
+             'prices': [{'id': 1111}, {'id': 2222}],
+             'quantity': 2,
+             'useHourlyPricing': True}
+        ]}
+
+        mock_pkg, mock_preset, mock_get_ids = self._patch_for_generate()
+
+        order = self.ordering.generate_order(pkg, 'DALLAS13', items, complex_type=complex_type, quantity=quantity,
+                                             extras=extras)
+
+        mock_pkg.assert_called_once_with(pkg, mask='id')
+        mock_preset.assert_not_called()
+        mock_get_ids.assert_called_once_with(pkg, items, None)
+        self.assertEqual(expected_order, order)
+
     def test_generate_order(self):
         pkg = 'PACKAGE_KEYNAME'
         items = ['ITEM1', 'ITEM2']
@@ -444,6 +474,33 @@ def test_place_order(self):
                                          extras=extras, quantity=quantity)
         self.assertEqual(ord_mock.return_value, order)
 
+    def test_place_order_with_quantity(self):
+        ord_mock = self.set_mock('SoftLayer_Product_Order', 'placeOrder')
+        ord_mock.return_value = {'id': 1234}
+        pkg = 'PACKAGE_KEYNAME'
+        location = 'DALLAS13'
+        items = ['ITEM1', 'ITEM2']
+        hourly = True
+        preset_keyname = 'PRESET'
+        complex_type = 'Complex_Type'
+        extras = {"hardware": [{"hostname": "test01", "domain": "example.com"},
+                               {"hostname": "test02", "domain": "example.com"}]}
+        quantity = 2
+
+        with mock.patch.object(self.ordering, 'generate_order') as gen_mock:
+            gen_mock.return_value = {'order': {}}
+
+            order = self.ordering.place_order(pkg, location, items, hourly=hourly,
+                                              preset_keyname=preset_keyname,
+                                              complex_type=complex_type,
+                                              extras=extras, quantity=quantity)
+
+        gen_mock.assert_called_once_with(pkg, location, items, hourly=hourly,
+                                         preset_keyname=preset_keyname,
+                                         complex_type=complex_type,
+                                         extras=extras, quantity=quantity)
+        self.assertEqual(ord_mock.return_value, order)
+
     def test_place_quote(self):
         ord_mock = self.set_mock('SoftLayer_Product_Order', 'placeQuote')
         ord_mock.return_value = {'id': 1234}