Merge branch 'feature/6561/show-multiple-line-items' into 'master'
Feature/6561/show multiple line items See merge request ungleich-public/dynamicweb!697
This commit is contained in:
		
				commit
				
					
						3c4494f35c
					
				
			
		
					 5 changed files with 169 additions and 70 deletions
				
			
		| 
						 | 
				
			
			@ -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: {}<br/>".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 += ("<b>Cores</b>: {}<br/><b>RAM</b>: {} GB<br/>"
 | 
			
		||||
                            "<b>SSD</b>: {} GB<br/>").format(
 | 
			
		||||
                vm_conf['cores'], int(float(vm_conf['ram'])), vm_conf['ssd']
 | 
			
		||||
            )
 | 
			
		||||
        return item_detail
 | 
			
		||||
 | 
			
		||||
class VMDetail(models.Model):
 | 
			
		||||
    user = models.ForeignKey(CustomUser)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -89,80 +89,91 @@
 | 
			
		|||
        <div>
 | 
			
		||||
            <h4>{% trans "Invoice summary" %}</h4>
 | 
			
		||||
            {% if vm %}
 | 
			
		||||
            <p>
 | 
			
		||||
                <strong>{% trans "Product" %}:</strong> 
 | 
			
		||||
                {% if vm.name %}
 | 
			
		||||
                {{ vm.name }}
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </p>
 | 
			
		||||
            <div class="row">
 | 
			
		||||
                <div class="col-sm-6">
 | 
			
		||||
                    {% if period_start %}
 | 
			
		||||
                {% if line_items %}
 | 
			
		||||
                    <table>
 | 
			
		||||
                        <tr><th style="width: 35%">Product</th><th style="width: 20%">Period</th><th style="text-align: center; width: 10%">Qty</th><th align="center"  style="width: 10%; text-align: center;">Unit Price</th><th  style="width: 10%; text-align: right;">Total</th></tr>
 | 
			
		||||
                        {% for line_item in line_items %}
 | 
			
		||||
                        <tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf}}</td></tr>
 | 
			
		||||
 | 
			
		||||
                        {% endfor %}
 | 
			
		||||
                        <tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf}}</td></tr>
 | 
			
		||||
                    </table>
 | 
			
		||||
                {% else %}
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <span>{% trans "Period" %}: </span>
 | 
			
		||||
                        <span>
 | 
			
		||||
                                        <span class="locale_date"
 | 
			
		||||
                                              data-format="YYYY/MM/DD">{{ period_start|date:'Y-m-d h:i a' }}</span> - <span
 | 
			
		||||
                                class="locale_date" data-format="YYYY/MM/DD">{{ period_end|date:'Y-m-d h:i a' }}</span>
 | 
			
		||||
                                    </span>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <span>{% trans "Cores" %}: </span>
 | 
			
		||||
                        {% if vm.cores %}
 | 
			
		||||
                        <strong class="pull-right">{{vm.cores|floatformat}}</strong>
 | 
			
		||||
                        {% else %}
 | 
			
		||||
                        <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
 | 
			
		||||
                        <strong>{% trans "Product" %}:</strong> 
 | 
			
		||||
                        {% if vm.name %}
 | 
			
		||||
                        {{ vm.name }}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <span>{% trans "Memory" %}: </span>
 | 
			
		||||
                        <strong class="pull-right">{{vm.memory}} GB</strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
                    <p>
 | 
			
		||||
                        <span>{% trans "Disk space" %}: </span>
 | 
			
		||||
                        <strong class="pull-right">{{vm.disk_size}} GB</strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-sm-12">
 | 
			
		||||
                    <hr class="thin-hr">
 | 
			
		||||
                </div>
 | 
			
		||||
                {% if vm.vat > 0 or vm.discount.amount > 0 %}
 | 
			
		||||
                <div class="col-sm-6">
 | 
			
		||||
                    <div class="subtotal-price">
 | 
			
		||||
                        {% if vm.vat > 0 %}
 | 
			
		||||
                        <p>
 | 
			
		||||
                            <strong>{% trans "Subtotal" %} </strong>
 | 
			
		||||
                            <strong class="pull-right">{{vm.price|floatformat:2|intcomma}}
 | 
			
		||||
                                CHF</strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%)
 | 
			
		||||
                            </small>
 | 
			
		||||
                            <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        {% if vm.discount.amount > 0 %}
 | 
			
		||||
                        <p class="text-primary">
 | 
			
		||||
                            {%trans "Discount" as discount_name %}
 | 
			
		||||
                            <strong>{{ vm.discount.name|default:discount_name }} </strong>
 | 
			
		||||
                            <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
 | 
			
		||||
                        </p>
 | 
			
		||||
                    <div class="row">
 | 
			
		||||
                        <div class="col-sm-6">
 | 
			
		||||
                            {% if period_start %}
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <span>{% trans "Period" %}: </span>
 | 
			
		||||
                                <span>
 | 
			
		||||
                                                <span class="locale_date"
 | 
			
		||||
                                                      data-format="YYYY/MM/DD">{{ period_start|date:'Y-m-d h:i a' }}</span> - <span
 | 
			
		||||
                                        class="locale_date" data-format="YYYY/MM/DD">{{ period_end|date:'Y-m-d h:i a' }}</span>
 | 
			
		||||
                                            </span>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            {% endif %}
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <span>{% trans "Cores" %}: </span>
 | 
			
		||||
                                {% if vm.cores %}
 | 
			
		||||
                                <strong class="pull-right">{{vm.cores|floatformat}}</strong>
 | 
			
		||||
                                {% else %}
 | 
			
		||||
                                <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <span>{% trans "Memory" %}: </span>
 | 
			
		||||
                                <strong class="pull-right">{{vm.memory}} GB</strong>
 | 
			
		||||
                            </p>
 | 
			
		||||
                            <p>
 | 
			
		||||
                                <span>{% trans "Disk space" %}: </span>
 | 
			
		||||
                                <strong class="pull-right">{{vm.disk_size}} GB</strong>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-sm-12">
 | 
			
		||||
                            <hr class="thin-hr">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {% if vm.vat > 0 or vm.discount.amount > 0 %}
 | 
			
		||||
                        <div class="col-sm-6">
 | 
			
		||||
                            <div class="subtotal-price">
 | 
			
		||||
                                {% if vm.vat > 0 %}
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    <strong>{% trans "Subtotal" %} </strong>
 | 
			
		||||
                                    <strong class="pull-right">{{vm.price|floatformat:2|intcomma}}
 | 
			
		||||
                                        CHF</strong>
 | 
			
		||||
                                </p>
 | 
			
		||||
                                <p>
 | 
			
		||||
                                    <small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%)
 | 
			
		||||
                                    </small>
 | 
			
		||||
                                    <strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
 | 
			
		||||
                                </p>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                                {% if vm.discount.amount > 0 %}
 | 
			
		||||
                                <p class="text-primary">
 | 
			
		||||
                                    {%trans "Discount" as discount_name %}
 | 
			
		||||
                                    <strong>{{ vm.discount.name|default:discount_name }} </strong>
 | 
			
		||||
                                    <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
 | 
			
		||||
                                </p>
 | 
			
		||||
                                {% endif %}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                        <div class="col-sm-12">
 | 
			
		||||
                            <hr class="thin-hr">
 | 
			
		||||
                        </div>
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                        <div class="col-sm-6">
 | 
			
		||||
                            <p class="total-price">
 | 
			
		||||
                                <strong>{% trans "Total" %} </strong>
 | 
			
		||||
                                <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
 | 
			
		||||
                                    CHF</strong>
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="col-sm-12">
 | 
			
		||||
                    <hr class="thin-hr">
 | 
			
		||||
                </div>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
                <div class="col-sm-6">
 | 
			
		||||
                    <p class="total-price">
 | 
			
		||||
                        <strong>{% trans "Total" %} </strong>
 | 
			
		||||
                        <strong class="pull-right">{% if vm.total_price %}{{vm.total_price|floatformat:2|intcomma}}{% else %}{{vm.price|floatformat:2|intcomma}}{% endif %}
 | 
			
		||||
                            CHF</strong>
 | 
			
		||||
                    </p>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% else %}
 | 
			
		||||
            <p>
 | 
			
		||||
                <strong>{% trans "Product" %}:</strong> 
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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):
 | 
			
		||||
        """
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue