Fix the way we calculate VAT and final VM price

1. Get the VM's pricing from its config and VMPricing
2. Compute and apply discount if any
3. Apply VAT on the discounted amount from 2
This commit is contained in:
PCoder 2019-01-12 11:02:33 +01:00
parent ef0e3b76fa
commit 484bd53bd2
5 changed files with 60 additions and 61 deletions

View file

@ -98,22 +98,26 @@
{% if vm.vat > 0 or vm.discount.amount > 0 %} {% if vm.vat > 0 or vm.discount.amount > 0 %}
<div class="col-sm-6"> <div class="col-sm-6">
<div class="subtotal-price"> <div class="subtotal-price">
{% if vm.vat > 0 %} <p>
<p> <strong>{% trans "Subtotal" %} </strong>
<strong class="text-lg">{% trans "Subtotal" %} </strong> <strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong> </p>
</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 %} {% if vm.discount.amount > 0 %}
<p class="text-primary"> <p class="text-primary">
{%trans "Discount" as discount_name %} {%trans "Discount" as discount_name %}
<strong>{{ vm.discount.name|default:discount_name }} </strong> <strong>{{ vm.discount.name|default:discount_name }} </strong>
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong> <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
</p> </p>
<p>
<small>{% trans "Subtotal after discount" %}</small>
<strong class="pull-right">{{vm.total_after_discount|floatformat:2|intcomma}} CHF</strong>
</p>
{% endif %}
{% if vm.vat > 0 %}
<p>
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -183,7 +183,7 @@ class IndexView(CreateView):
) )
return HttpResponseRedirect(referer_url + "#order_form") return HttpResponseRedirect(referer_url + "#order_form")
price, vat, vat_percent, discount = get_vm_price_with_vat( vm_price_dict = get_vm_price_with_vat(
cpu=cores, cpu=cores,
memory=memory, memory=memory,
ssd_size=storage, ssd_size=storage,
@ -193,13 +193,9 @@ class IndexView(CreateView):
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, 'disk_size': storage,
'price': price,
'vat': vat,
'vat_percent': vat_percent,
'discount': discount,
'total_price': round(price + vat - discount['amount'], 2),
'pricing_name': vm_pricing_name 'pricing_name': vm_pricing_name
} }
specs.update(vm_price_dict)
request.session['specs'] = specs request.session['specs'] = specs
request.session['template'] = template_data request.session['template'] = template_data
return HttpResponseRedirect(reverse('datacenterlight:payment')) return HttpResponseRedirect(reverse('datacenterlight:payment'))

View file

@ -135,22 +135,26 @@
{% if vm.vat > 0 or vm.discount.amount > 0 %} {% if vm.vat > 0 or vm.discount.amount > 0 %}
<div class="col-sm-6"> <div class="col-sm-6">
<div class="subtotal-price"> <div class="subtotal-price">
{% if vm.vat > 0 %} <p>
<p> <strong>{% trans "Subtotal" %} </strong>
<strong>{% trans "Subtotal" %} </strong> <strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong> </p>
</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 %} {% if vm.discount.amount > 0 %}
<p class="text-primary"> <p class="text-primary">
{%trans "Discount" as discount_name %} {%trans "Discount" as discount_name %}
<strong>{{ vm.discount.name|default:discount_name }} </strong> <strong>{{ vm.discount.name|default:discount_name }} </strong>
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong> <strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
</p> </p>
<p>
<small>{% trans "Subtotal after discount" %}</small>
<strong class="pull-right">{{vm.total_after_discount|floatformat:2|intcomma}} CHF</strong>
</p>
{% endif %}
{% if vm.vat > 0 %}
<p>
<small>{% trans "VAT" %} ({{ vm.vat_percent|floatformat:2|intcomma }}%) </small>
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -878,18 +878,14 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
context['vm'] = vm_detail.__dict__ context['vm'] = vm_detail.__dict__
context['vm']['name'] = '{}-{}'.format( context['vm']['name'] = '{}-{}'.format(
context['vm']['configuration'], context['vm']['vm_id']) context['vm']['configuration'], context['vm']['vm_id'])
price, vat, vat_percent, discount = get_vm_price_with_vat( vm_price_dict = get_vm_price_with_vat(
cpu=context['vm']['cores'], cpu=context['vm']['cores'],
ssd_size=context['vm']['disk_size'], ssd_size=context['vm']['disk_size'],
memory=context['vm']['memory'], memory=context['vm']['memory'],
pricing_name=(obj.vm_pricing.name pricing_name=(obj.vm_pricing.name
if obj.vm_pricing else 'default') if obj.vm_pricing else 'default')
) )
context['vm']['vat'] = vat context['vm'] = vm_price_dict
context['vm']['price'] = price
context['vm']['discount'] = discount
context['vm']['vat_percent'] = vat_percent
context['vm']['total_price'] = price + vat - discount['amount']
context['subscription_end_date'] = vm_detail.end_date() context['subscription_end_date'] = vm_detail.end_date()
except VMDetail.DoesNotExist: except VMDetail.DoesNotExist:
try: try:
@ -898,20 +894,14 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView):
) )
vm = manager.get_vm(obj.vm_id) vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data context['vm'] = VirtualMachineSerializer(vm).data
price, vat, vat_percent, discount = get_vm_price_with_vat( vm_price_dict = get_vm_price_with_vat(
cpu=context['vm']['cores'], cpu=context['vm']['cores'],
ssd_size=context['vm']['disk_size'], ssd_size=context['vm']['disk_size'],
memory=context['vm']['memory'], memory=context['vm']['memory'],
pricing_name=(obj.vm_pricing.name pricing_name=(obj.vm_pricing.name
if obj.vm_pricing else 'default') if obj.vm_pricing else 'default')
) )
context['vm']['vat'] = vat context['vm'] = vm_price_dict
context['vm']['price'] = price
context['vm']['discount'] = discount
context['vm']['vat_percent'] = vat_percent
context['vm']['total_price'] = (
price + vat - discount['amount']
)
except WrongIdError: except WrongIdError:
messages.error( messages.error(
self.request, self.request,
@ -1290,25 +1280,19 @@ class CreateVirtualMachinesView(LoginRequiredMixin, View):
extra_tags='storage') extra_tags='storage')
return redirect(CreateVirtualMachinesView.as_view()) return redirect(CreateVirtualMachinesView.as_view())
price, vat, vat_percent, discount = get_vm_price_with_vat( vm_price_dict = get_vm_price_with_vat(
cpu=cores, cpu=cores,
memory=memory, memory=memory,
ssd_size=storage, ssd_size=storage,
pricing_name=vm_pricing_name pricing_name=vm_pricing_name
) )
specs = { specs = {
'cpu': cores, 'cpu': cores,
'memory': memory, 'memory': memory,
'disk_size': storage, 'disk_size': storage,
'discount': discount, 'pricing_name': vm_pricing_name,
'price': price,
'vat': vat,
'vat_percent': vat_percent,
'total_price': round(price + vat - discount['amount'], 2),
'pricing_name': vm_pricing_name
} }
specs.update(vm_price_dict)
request.session['specs'] = specs request.session['specs'] = specs
request.session['template'] = template_data request.session['template'] = template_data
return redirect(reverse('hosting:payment')) return redirect(reverse('hosting:payment'))

View file

@ -95,8 +95,14 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
:param ssd_size: Disk space of the VM (SSD) :param ssd_size: Disk space of the VM (SSD)
:param hdd_size: The HDD size :param hdd_size: The HDD size
:param pricing_name: The pricing name to be used :param pricing_name: The pricing name to be used
:return: The a tuple containing the price of the VM, the VAT and the :return: A dict containing
VAT percentage 1. the price of the VM,
2. the applied VAT amount
3. the VAT percentage
4. the discount (a dict containing the discount name and
the discount amount),
5. the price after discount
6. the final price
""" """
try: try:
pricing = VMPricing.objects.get(name=pricing_name) pricing = VMPricing.objects.get(name=pricing_name)
@ -109,28 +115,33 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0,
) )
return None return None
price = ( price = decimal.Decimal(
(decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(cpu) * pricing.cores_unit_price) +
(decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) +
(decimal.Decimal(hdd_size) * pricing.hdd_unit_price) (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)
) )
discount = {
'name': pricing.discount_name,
'amount': round(pricing.discount_amount, 2)
}
price_after_discount = price - discount['amount']
if pricing.vat_inclusive: if pricing.vat_inclusive:
vat = decimal.Decimal(0) vat = decimal.Decimal(0)
vat_percent = decimal.Decimal(0) vat_percent = decimal.Decimal(0)
else: else:
vat = price * pricing.vat_percentage * decimal.Decimal(0.01) vat = price_after_discount * pricing.vat_percentage * decimal.Decimal(0.01)
vat_percent = pricing.vat_percentage vat_percent = pricing.vat_percentage
cents = decimal.Decimal('.01') return {
price = price.quantize(cents, decimal.ROUND_HALF_UP) 'price': round(price, 2),
vat = vat.quantize(cents, decimal.ROUND_HALF_UP) 'vat': round(vat, 2),
discount = { 'vat_percent': round(vat_percent, 2),
'name': pricing.discount_name, 'discount': discount,
'amount': round(float(pricing.discount_amount), 2) 'price_after_discount': round(price_after_discount, 2),
'total_price': round(price_after_discount + vat, 2)
} }
return (round(float(price), 2), round(float(vat), 2),
round(float(vat_percent), 2), discount)
def ping_ok(host_ipv6): def ping_ok(host_ipv6):