From e782d2773909eb92505e12c24fdd8a2b93fbba78 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 20 Apr 2019 15:20:55 +0200 Subject: [PATCH 1/6] Attempt to show relevant lines when we have more than 1 line item --- hosting/templates/hosting/invoice_detail.html | 146 ++++++++++-------- hosting/views.py | 3 + 2 files changed, 81 insertions(+), 68 deletions(-) diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html index e63f25a7..359178b2 100644 --- a/hosting/templates/hosting/invoice_detail.html +++ b/hosting/templates/hosting/invoice_detail.html @@ -89,80 +89,90 @@

{% trans "Invoice summary" %}

{% if vm %} -

- {% trans "Product" %}:  - {% if vm.name %} - {{ vm.name }} - {% endif %} -

-
-
- {% if period_start %} + {% if line_items %} + + + {% for line_item in line_items %} + + {% endfor %} + +
ProductPeriodQuantityUnit PriceTotal
{% if line_item.description|len > 0 %}{{line_item.description}}{% else %}{{line_item.stripe_plan.stripe_plan_id}}{% endif %}{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}{{line_item.quantity}}{{line_item.unit_amount}}{{line_item.amount}}
Grand Total{{total_in_chf}}
+ {% else %}

- {% trans "Period" %}: - - {{ period_start|date:'Y-m-d h:i a' }} - {{ period_end|date:'Y-m-d h:i a' }} - -

- {% endif %} -

- {% trans "Cores" %}: - {% if vm.cores %} - {{vm.cores|floatformat}} - {% else %} - {{vm.cpu|floatformat}} + {% trans "Product" %}:  + {% if vm.name %} + {{ vm.name }} {% endif %}

-

- {% trans "Memory" %}: - {{vm.memory}} GB -

-

- {% trans "Disk space" %}: - {{vm.disk_size}} GB -

-
-
-
-
- {% if vm.vat > 0 or vm.discount.amount > 0 %} -
-
- {% if vm.vat > 0 %} -

- {% trans "Subtotal" %} - {{vm.price|floatformat:2|intcomma}} - CHF -

-

- {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) - - {{vm.vat|floatformat:2|intcomma}} CHF -

- {% endif %} - {% if vm.discount.amount > 0 %} -

- {%trans "Discount" as discount_name %} - {{ vm.discount.name|default:discount_name }} - - {{ vm.discount.amount }} CHF -

+
+
+ {% if period_start %} +

+ {% trans "Period" %}: + + {{ period_start|date:'Y-m-d h:i a' }} - {{ period_end|date:'Y-m-d h:i a' }} + +

+ {% endif %} +

+ {% trans "Cores" %}: + {% if vm.cores %} + {{vm.cores|floatformat}} + {% else %} + {{vm.cpu|floatformat}} + {% endif %} +

+

+ {% trans "Memory" %}: + {{vm.memory}} GB +

+

+ {% trans "Disk space" %}: + {{vm.disk_size}} GB +

+
+
+
+
+ {% if vm.vat > 0 or vm.discount.amount > 0 %} +
+
+ {% if vm.vat > 0 %} +

+ {% trans "Subtotal" %} + {{vm.price|floatformat:2|intcomma}} + CHF +

+

+ {% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) + + {{vm.vat|floatformat:2|intcomma}} CHF +

+ {% endif %} + {% if vm.discount.amount > 0 %} +

+ {%trans "Discount" as discount_name %} + {{ vm.discount.name|default:discount_name }} + - {{ vm.discount.amount }} CHF +

+ {% endif %} +
+
+
+
+
{% endif %} +
+

+ {% trans "Total" %} + {% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} + CHF +

+
-
-
-
-
{% endif %} -
-

- {% trans "Total" %} - {% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %} - CHF -

-
-
{% else %}

{% trans "Product" %}:  diff --git a/hosting/views.py b/hosting/views.py index fe13ff21..862989e1 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1322,6 +1322,9 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): context['total_in_chf'] = obj.total_in_chf() context['invoice_number'] = obj.invoice_number context['discount_on_stripe'] = obj.discount_in_chf() + if obj.lines_data_count > 1: + # special case, we pass the details of each of the line items + context['line_items'] = obj.hostingbilllineitem_set.all() return context else: raise Http404 From a811e9f83d245ff0bbac9f7fdb978a3f4dbd3b0c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 20 Apr 2019 18:50:46 +0200 Subject: [PATCH 2/6] Add helper methods in HostingBillLineItem --- hosting/models.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/hosting/models.py b/hosting/models.py index d4a51763..19e73cca 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -473,6 +473,51 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model): ('view_hostingbilllineitem', 'View Monthly Hosting Bill Line Item'), ) + def amount_in_chf(self): + """ + Returns amount in chf. The amount in this model is in cents (as in + Stripe). Hence we multiply it by 0.01 to obtain the result + + :return: + """ + return self.amount * 0.01 + + def unit_amount_in_chf(self): + """ + Returns unit amount in chf. If its 0, we obtain it from amount and + quantity. + + :return: + """ + if self.unit_amount == 0: + return round((self.amount / self.quantity) * 0.01, 2) + else: + return self.unit_amount * 0.01 + + def get_item_detail_str(self): + """ + Returns line item html string representation + :return: + """ + item_detail = "" + if self.metadata is not None and len(self.metadata) > 0: + try: + vm_dict = json.loads(self.metadata) + item_detail = "VM ID: {}
".format(vm_dict["VM_ID"]) + except ValueError as ve: + logger.error( + "Could not parse VM in metadata {}. Detail {}".format( + self.metadata, str(ve) + ) + ) + vm_conf = StripeUtils.get_vm_config_from_stripe_id( + self.stripe_plan.stripe_plan_id + ) + item_detail += ("Cores: {}
RAM: {} GB
" + "SSD: {} GB
").format( + vm_conf['cores'], int(float(vm_conf['ram'])), vm_conf['ssd'] + ) + return item_detail class VMDetail(models.Model): user = models.ForeignKey(CustomUser) From 2ae0c8629bee9919fc71e56dfdf641aabf44d917 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 20 Apr 2019 18:51:23 +0200 Subject: [PATCH 3/6] Add CSS styles for multi line invoice table --- hosting/static/hosting/css/order.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hosting/static/hosting/css/order.css b/hosting/static/hosting/css/order.css index 8aafb8a8..e68ade33 100644 --- a/hosting/static/hosting/css/order.css +++ b/hosting/static/hosting/css/order.css @@ -112,4 +112,17 @@ .dcl-place-order-text { color: #808080; +} + +table { + border-collapse: collapse; +} + +tr.border_bottom td { + border-bottom:1pt solid #eee; +} + +tr.grand-total-padding td { + padding-top: 10px; + font-weight: bold; } \ No newline at end of file From a1a85e6c188410a2b4ad6ab528024d937f66d79a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 20 Apr 2019 18:52:01 +0200 Subject: [PATCH 4/6] Style the invoice detail page for multiline items --- hosting/templates/hosting/invoice_detail.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html index 359178b2..d1411db0 100644 --- a/hosting/templates/hosting/invoice_detail.html +++ b/hosting/templates/hosting/invoice_detail.html @@ -91,11 +91,12 @@ {% if vm %} {% if line_items %} - + {% for line_item in line_items %} - + + {% endfor %} - +
ProductPeriodQuantityUnit PriceTotal
ProductPeriodQtyUnit PriceTotal
{% if line_item.description|len > 0 %}{{line_item.description}}{% else %}{{line_item.stripe_plan.stripe_plan_id}}{% endif %}{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}{{line_item.quantity}}{{line_item.unit_amount}}{{line_item.amount}}
{% if line_item.description|length > 0 %}{{line_item.description}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}{{line_item.quantity}}{{line_item.unit_amount_in_chf}}{{line_item.amount_in_chf}}
Grand Total{{total_in_chf}}
Grand Total{{total_in_chf}}
{% else %}

From 0b99a0cbece3101d961c1c7e9859eb747f86539f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 20 Apr 2019 18:52:34 +0200 Subject: [PATCH 5/6] Fix getting users email and password from the request object --- hosting/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 862989e1..2d7a3093 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1284,8 +1284,8 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): # fallback to get it from the infrastructure try: manager = OpenNebulaManager( - email=self.request.email, - password=self.request.password + email=self.request.user.email, + password=self.request.user.password ) vm = manager.get_vm(vm_id) context['vm'] = VirtualMachineSerializer(vm).data From 21eb88ef624fe99d1bc39ed838671271a948d42a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 20 Apr 2019 18:53:20 +0200 Subject: [PATCH 6/6] Add get_vm_config_from_stripe_id stripe util function --- utils/stripe_utils.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index a3682514..4334d6cf 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -1,4 +1,5 @@ import logging +import re import stripe from django.conf import settings from datacenterlight.models import StripePlan @@ -376,6 +377,32 @@ class StripeUtils(object): else: return stripe_plan_id_string + @staticmethod + def get_vm_config_from_stripe_id(stripe_id): + """ + Given a string like "dcl-v1-cpu-2-ram-5gb-ssd-10gb" return different + configuration params as a dict + + :param stripe_id|str + :return: dict + """ + pattern = re.compile(r'^dcl-v(\d+)-cpu-(\d+)-ram-(\d+\.?\d*)gb-ssd-(\d+)gb-?(\d*\.?\d*)(chf)?$') + match_res = pattern.match(stripe_id) + if match_res is not None: + price = None + try: + price=match_res.group(5) + except IndexError as ie: + logger.debug("Did not find price in {}".format(stripe_id)) + return { + 'version': match_res.group(1), + 'cores': match_res.group(2), + 'ram': match_res.group(3), + 'ssd': match_res.group(4), + 'price': price + } + + @staticmethod def get_stripe_plan_name(cpu, memory, disk_size, price): """