Support invisible products and fix the units
Usercase of invisible products: As a cloud provider, I'd like to hide some products which I don't want to expose to project users. In the patch, the units in invoice API is also fixed by getting the units info from product's internal reference. Change-Id: Iff05f61c0bc303a100f477696dcc904b64c9724d
This commit is contained in:
parent
d3d04fbe1d
commit
e5bd5b8213
|
@ -96,6 +96,12 @@ ODOO_OPTS = [
|
|||
cfg.StrOpt('object_storage_service_name',
|
||||
default='o1.standard',
|
||||
help='Service name for object storage.'),
|
||||
cfg.ListOpt('invisible_products', default=['reseller-margin-discount'],
|
||||
help=("The product list which will be invisible to project "
|
||||
"users. For example, as a cloud provider we would like "
|
||||
"to hide the reseller margin for reseller's customer.")),
|
||||
cfg.FloatOpt('tax_rate', default='0.15',
|
||||
help='Tax rate for invoicing.'),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ class OdooDriver(driver.BaseDriver):
|
|||
self.credit = self.odoo.env['cloud.credit']
|
||||
|
||||
self.product_category_mapping = {}
|
||||
self.product_unit_mapping = {}
|
||||
|
||||
def is_healthy(self):
|
||||
try:
|
||||
|
@ -94,6 +95,7 @@ class OdooDriver(driver.BaseDriver):
|
|||
@cache.memoize
|
||||
def get_products(self, regions=[]):
|
||||
self.product_category_mapping.clear()
|
||||
self.product_unit_mapping.clear()
|
||||
odoo_regions = []
|
||||
|
||||
if not regions:
|
||||
|
@ -144,6 +146,7 @@ class OdooDriver(driver.BaseDriver):
|
|||
# Odoo GUI
|
||||
unit = product['default_code']
|
||||
desc = product['description']
|
||||
self.product_unit_mapping[product['id']] = unit
|
||||
|
||||
prices[actual_region][category.lower()].append(
|
||||
{'name': name,
|
||||
|
@ -189,7 +192,11 @@ class OdooDriver(driver.BaseDriver):
|
|||
def _get_invoice_detail(self, invoice_id):
|
||||
"""Get invoice details.
|
||||
|
||||
Return details in the following format:
|
||||
Two results will be returned: detail_dict and invisible_cost.
|
||||
invisible_cost is the total cost of those products which cloud
|
||||
providers don't want to show in the invoice API. It's a number
|
||||
and has been revised based on give tax rate. The format of
|
||||
detail_dict is as below:
|
||||
{
|
||||
'category': {
|
||||
'total_cost': xxx,
|
||||
|
@ -217,6 +224,13 @@ class OdooDriver(driver.BaseDriver):
|
|||
}
|
||||
"""
|
||||
detail_dict = {}
|
||||
# NOTE(flwang): To hide some cost like 'reseller_margin_discount', we
|
||||
# need to get the total amount for those cost/usage and then
|
||||
# re-calculate the total cost for the monthly cost.
|
||||
# Because the total cost in the final invoice is got from odoo, so it
|
||||
# includes tax(GST). So we also need to include tax when re-calculate
|
||||
# the total cost.
|
||||
invisible_cost = 0
|
||||
|
||||
invoice_lines_ids = self.invoice_line.search(
|
||||
[('invoice_id', '=', invoice_id)]
|
||||
|
@ -228,7 +242,12 @@ class OdooDriver(driver.BaseDriver):
|
|||
'resource_name': line['name'],
|
||||
'quantity': round(line['quantity'], constants.QUANTITY_DIGITS),
|
||||
'rate': round(line['price_unit'], constants.RATE_DIGITS),
|
||||
'unit': line['uos_id'][1],
|
||||
# TODO(flwang): We're not exposing some product at all, such
|
||||
# as the discount product. For those kind of product, using
|
||||
# NZD as the default. We may have to revisit this part later
|
||||
# if there is new requirement.
|
||||
'unit': self.product_unit_mapping.get(line['product_id'][0],
|
||||
'NZD'),
|
||||
'cost': round(line['price_subtotal'], constants.PRICE_DIGITS)
|
||||
}
|
||||
|
||||
|
@ -236,21 +255,25 @@ class OdooDriver(driver.BaseDriver):
|
|||
if re.match(r"\[.+\].+", product):
|
||||
product = product.split(']')[1].strip()
|
||||
|
||||
category = self.product_category_mapping[line['product_id'][0]]
|
||||
if product in self.conf.odoo.invisible_products:
|
||||
invisible_cost += -(line_info['cost'] *
|
||||
(1 + self.conf.odoo.tax_rate))
|
||||
else:
|
||||
category = self.product_category_mapping[line['product_id'][0]]
|
||||
|
||||
if category not in detail_dict:
|
||||
detail_dict[category] = {
|
||||
'total_cost': 0,
|
||||
'breakdown': collections.defaultdict(list)
|
||||
}
|
||||
if category not in detail_dict:
|
||||
detail_dict[category] = {
|
||||
'total_cost': 0,
|
||||
'breakdown': collections.defaultdict(list)
|
||||
}
|
||||
|
||||
detail_dict[category]['total_cost'] = round(
|
||||
(detail_dict[category]['total_cost'] + line_info['cost']),
|
||||
constants.PRICE_DIGITS
|
||||
)
|
||||
detail_dict[category]['breakdown'][product].append(line_info)
|
||||
detail_dict[category]['total_cost'] = round(
|
||||
(detail_dict[category]['total_cost'] + line_info['cost']),
|
||||
constants.PRICE_DIGITS
|
||||
)
|
||||
detail_dict[category]['breakdown'][product].append(line_info)
|
||||
|
||||
return detail_dict
|
||||
return detail_dict, invisible_cost
|
||||
|
||||
@cache.memoize
|
||||
def get_invoices(self, start, end, project_id, detailed=False):
|
||||
|
@ -319,7 +342,12 @@ class OdooDriver(driver.BaseDriver):
|
|||
if not self.product_category_mapping:
|
||||
self.get_products()
|
||||
|
||||
details = self._get_invoice_detail(v['id'])
|
||||
details, invisible_cost = self._get_invoice_detail(v['id'])
|
||||
# NOTE(flwang): Revise the total cost based on the
|
||||
# invisible cost
|
||||
m = result[v['date_invoice']]
|
||||
m['total_cost'] = round(m['total_cost'] + invisible_cost,
|
||||
constants.PRICE_DIGITS)
|
||||
result[v['date_invoice']].update({'details': details})
|
||||
except Exception as e:
|
||||
LOG.exception(
|
||||
|
|
|
@ -116,6 +116,7 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
|
||||
odoodriver = odoo.OdooDriver(self.conf)
|
||||
odoodriver.invoice.search.return_value = ['1', '2']
|
||||
odoodriver.product_unit_mapping = {1: 'hour'}
|
||||
odoodriver.invoice_line.read.side_effect = [
|
||||
[
|
||||
{
|
||||
|
@ -138,7 +139,7 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
[
|
||||
{
|
||||
'name': 'resource3',
|
||||
'quantity': 653.2345,
|
||||
'quantity': 3,
|
||||
'price_unit': 0.123,
|
||||
'uos_id': [1, 'Gigabyte-hour(s)'],
|
||||
'price_subtotal': 0.369,
|
||||
|
@ -146,10 +147,10 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
},
|
||||
{
|
||||
'name': 'resource4',
|
||||
'quantity': 4,
|
||||
'quantity': 40,
|
||||
'price_unit': 0.123,
|
||||
'uos_id': [1, 'Gigabyte-hour(s)'],
|
||||
'price_subtotal': 0.492,
|
||||
'price_subtotal': 4.92,
|
||||
'product_id': [1, '[hour] NZ-POR-1.c1.c2r8']
|
||||
},
|
||||
{
|
||||
|
@ -159,13 +160,21 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
'uos_id': [1, 'Unit(s)'],
|
||||
"price_subtotal": -0.1,
|
||||
'product_id': [4, 'cloud-dev-grant']
|
||||
},
|
||||
{
|
||||
"name": "Reseller Margin discount",
|
||||
"quantity": 1,
|
||||
"price_unit": -1,
|
||||
'uos_id': [1, 'Unit(s)'],
|
||||
"price_subtotal": -1,
|
||||
'product_id': [8, 'reseller-margin-discount']
|
||||
}
|
||||
]
|
||||
]
|
||||
odoodriver.odoo.execute.return_value = [
|
||||
{'id': 1, 'date_invoice': '2017-03-31', 'amount_total': 0.371,
|
||||
{'id': 1, 'date_invoice': '2017-03-31', 'amount_total': 0.426,
|
||||
'state': 'paid'},
|
||||
{'id': 2, 'date_invoice': '2017-04-30', 'amount_total': 0.759,
|
||||
{'id': 2, 'date_invoice': '2017-04-30', 'amount_total': 4.817,
|
||||
'state': 'open'}
|
||||
]
|
||||
odoodriver.product_category_mapping = {
|
||||
|
@ -182,7 +191,7 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
self.assertEqual(
|
||||
{
|
||||
'2017-03-31': {
|
||||
'total_cost': 0.37,
|
||||
'total_cost': 0.43,
|
||||
'status': 'paid',
|
||||
'details': {
|
||||
'Compute': {
|
||||
|
@ -194,14 +203,14 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
"quantity": 1,
|
||||
"rate": 0.123,
|
||||
"resource_name": "resource1",
|
||||
"unit": "Gigabyte-hour(s)"
|
||||
"unit": "hour"
|
||||
},
|
||||
{
|
||||
"cost": 0.25,
|
||||
"quantity": 2,
|
||||
"rate": 0.123,
|
||||
"resource_name": "resource2",
|
||||
"unit": "Gigabyte-hour(s)"
|
||||
"unit": "hour"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -209,7 +218,7 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
}
|
||||
},
|
||||
'2017-04-30': {
|
||||
'total_cost': 0.76,
|
||||
'total_cost': 5.97,
|
||||
'status': 'open',
|
||||
'details': {
|
||||
"Discounts":{
|
||||
|
@ -218,7 +227,7 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
'cloud-dev-grant': [
|
||||
{
|
||||
'quantity': 1.0,
|
||||
'unit': 'Unit(s)',
|
||||
'unit': 'NZD',
|
||||
'cost': -0.1,
|
||||
'resource_name': 'Development Grant',
|
||||
'rate': -0.1}
|
||||
|
@ -226,22 +235,22 @@ class TestOdooDriver(base.DistilTestCase):
|
|||
}
|
||||
},
|
||||
'Compute': {
|
||||
'total_cost': 0.86,
|
||||
'total_cost': 5.29,
|
||||
'breakdown': {
|
||||
'NZ-POR-1.c1.c2r8': [
|
||||
{
|
||||
"cost": 0.37,
|
||||
"quantity": 653.235,
|
||||
"quantity": 3,
|
||||
"rate": 0.123,
|
||||
"resource_name": "resource3",
|
||||
"unit": "Gigabyte-hour(s)"
|
||||
"unit": "hour"
|
||||
},
|
||||
{
|
||||
"cost": 0.49,
|
||||
"quantity": 4,
|
||||
"cost": 4.92,
|
||||
"quantity": 40,
|
||||
"rate": 0.123,
|
||||
"resource_name": "resource4",
|
||||
"unit": "Gigabyte-hour(s)"
|
||||
"unit": "hour"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue