improved pricelist view
This commit is contained in:
parent
3f3b9da992
commit
a8f204dcb4
2 changed files with 271 additions and 34 deletions
|
@ -1,5 +1,6 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load math_tags %}
|
||||||
|
|
||||||
{% block title %}Complete Price List{% endblock %}
|
{% block title %}Complete Price List{% endblock %}
|
||||||
|
|
||||||
|
@ -51,6 +52,69 @@
|
||||||
.servala-row {
|
.servala-row {
|
||||||
border-bottom: 2px solid #007bff;
|
border-bottom: 2px solid #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Price calculation breakdown styling */
|
||||||
|
.price-breakdown-header {
|
||||||
|
background: linear-gradient(135deg, #28a745, #20b2aa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compute-plan-col {
|
||||||
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
|
border-right: 2px solid #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sla-base-col {
|
||||||
|
background-color: rgba(111, 66, 193, 0.1);
|
||||||
|
border-right: 2px solid #6f42c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sla-units-col {
|
||||||
|
background-color: rgba(253, 126, 20, 0.1);
|
||||||
|
border-right: 2px solid #fd7e14;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mandatory-addons-col {
|
||||||
|
background-color: rgba(220, 53, 69, 0.1);
|
||||||
|
border-right: 2px solid #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-sla-col {
|
||||||
|
background-color: rgba(25, 135, 84, 0.2);
|
||||||
|
border-right: 3px solid #198754;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mathematical operator styling */
|
||||||
|
.math-operator {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Price calculation formula helper */
|
||||||
|
.price-formula {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-family: monospace;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-formula .formula-part {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
margin: 0 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-formula .compute-part { background-color: rgba(13, 110, 253, 0.2); color: #0d6efd; }
|
||||||
|
.price-formula .sla-base-part { background-color: rgba(111, 66, 193, 0.2); color: #6f42c1; }
|
||||||
|
.price-formula .sla-units-part { background-color: rgba(253, 126, 20, 0.2); color: #fd7e14; }
|
||||||
|
.price-formula .addons-part { background-color: rgba(220, 53, 69, 0.2); color: #dc3545; }
|
||||||
|
.price-formula .equals-part { background-color: rgba(25, 135, 84, 0.2); color: #198754; }
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -60,6 +124,61 @@
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h1 class="mb-4">Complete Price List - All Service Variants</h1>
|
<h1 class="mb-4">Complete Price List - All Service Variants</h1>
|
||||||
|
|
||||||
|
<!-- Pricing Model Explanation -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#pricingExplanation" aria-expanded="false" aria-controls="pricingExplanation" style="cursor: pointer;">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>How Our Pricing Works
|
||||||
|
<small class="text-muted ms-2">(Click to expand)</small>
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="collapse" id="pricingExplanation">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Price Components</h6>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge" style="background-color: #0d6efd;">Compute Plan Price</span>
|
||||||
|
<span class="ms-2">Base infrastructure cost for CPU, memory, and storage</span>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge" style="background-color: #6f42c1;">SLA Base</span>
|
||||||
|
<span class="ms-2">Fixed cost for the service level agreement</span>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge" style="background-color: #fd7e14;">Units × SLA Per Unit</span>
|
||||||
|
<span class="ms-2">Variable cost based on scale/usage</span>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<span class="badge" style="background-color: #dc3545;">Mandatory Add-ons</span>
|
||||||
|
<span class="ms-2">Required additional services (backup, monitoring, etc.)</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h6>Final Price Formula</h6>
|
||||||
|
<div class="bg-light p-3 rounded">
|
||||||
|
<code>
|
||||||
|
<span style="color: #0d6efd;">Compute Plan Price</span> +
|
||||||
|
<span style="color: #6f42c1;">SLA Base</span> +
|
||||||
|
<span style="color: #fd7e14;">(Units × SLA Per Unit)</span> +
|
||||||
|
<span style="color: #dc3545;">Mandatory Add-ons</span> =
|
||||||
|
<strong style="color: #198754;">Final Price</strong>
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 mb-0">
|
||||||
|
<small class="text-muted">
|
||||||
|
This transparent pricing model ensures you understand exactly what you're paying for.
|
||||||
|
The table below breaks down each component for every service variant we offer.
|
||||||
|
</small>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filter Form -->
|
<!-- Filter Form -->
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -278,32 +397,49 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
<!-- Price Calculation Formula Helper -->
|
||||||
|
<div class="price-formula">
|
||||||
|
<strong>Final Price Calculation:</strong><br>
|
||||||
|
<span class="formula-part compute-part">Compute Plan Price</span>
|
||||||
|
<span class="math-operator">+</span>
|
||||||
|
<span class="formula-part sla-base-part">SLA Base</span>
|
||||||
|
<span class="math-operator">+</span>
|
||||||
|
<span class="formula-part sla-units-part">Units × SLA Per Unit</span>
|
||||||
|
<span class="math-operator">+</span>
|
||||||
|
<span class="formula-part addons-part">Mandatory Add-ons</span>
|
||||||
|
<span class="math-operator">=</span>
|
||||||
|
<span class="formula-part equals-part">Final Price</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-bordered table-sm pricing-table">
|
<table class="table table-striped table-bordered table-sm pricing-table">
|
||||||
<thead class="table-dark">
|
<thead class="table-dark">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Compute Plan</th>
|
<th rowspan="2">Compute Plan</th>
|
||||||
<th>Cloud Provider</th>
|
<th rowspan="2">Cloud Provider</th>
|
||||||
<th>vCPUs</th>
|
<th rowspan="2">vCPUs</th>
|
||||||
<th>RAM (GB)</th>
|
<th rowspan="2">RAM (GB)</th>
|
||||||
<th>Term</th>
|
<th rowspan="2">Term</th>
|
||||||
<th>Currency</th>
|
<th rowspan="2">Currency</th>
|
||||||
<th>Compute Plan Price</th>
|
<th colspan="5" class="text-center" style="background-color: #198754 !important;">Price Calculation Breakdown</th>
|
||||||
<th>Units</th>
|
|
||||||
<th>SLA Base</th>
|
|
||||||
<th>SLA Per Unit</th>
|
|
||||||
<th>SLA Price</th>
|
|
||||||
{% if show_addon_details %}
|
{% if show_addon_details %}
|
||||||
<th>Add-ons</th>
|
<th rowspan="2">Add-ons</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if show_discount_details %}
|
{% if show_discount_details %}
|
||||||
<th>Discount Model</th>
|
<th rowspan="2">Discount Model</th>
|
||||||
<th>Discount Details</th>
|
<th rowspan="2">Discount Details</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if show_price_comparison %}
|
{% if show_price_comparison %}
|
||||||
<th>External Comparisons</th>
|
<th rowspan="2">External Comparisons</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<th class="final-price-header">Final Price</th>
|
<th rowspan="2" class="final-price-header">Final Price</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th style="background-color: #0d6efd; color: white;">Compute Plan Price</th>
|
||||||
|
<th style="background-color: #6f42c1; color: white;">SLA Base</th>
|
||||||
|
<th style="background-color: #fd7e14; color: white;">Units × SLA Per Unit</th>
|
||||||
|
<th style="background-color: #dc3545; color: white;">Mandatory Add-ons</th>
|
||||||
|
<th style="background-color: #198754; color: white;">= Total SLA Price</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -315,11 +451,44 @@
|
||||||
<td>{{ row.ram }}</td>
|
<td>{{ row.ram }}</td>
|
||||||
<td>{{ row.term }}</td>
|
<td>{{ row.term }}</td>
|
||||||
<td>{{ row.currency }}</td>
|
<td>{{ row.currency }}</td>
|
||||||
<td>{{ row.compute_plan_price|floatformat:2 }}</td>
|
<!-- Price Calculation Breakdown -->
|
||||||
<td>{{ row.units }}</td>
|
<td class="text-center" style="background-color: rgba(13, 110, 253, 0.1);">
|
||||||
<td>{{ row.sla_base|floatformat:2 }}</td>
|
<span class="fw-bold">{{ row.compute_plan_price|floatformat:2 }}</span>
|
||||||
<td>{{ row.sla_per_unit|floatformat:4 }}</td>
|
</td>
|
||||||
<td>{{ row.sla_price|floatformat:2 }}</td>
|
<td class="text-center" style="background-color: rgba(111, 66, 193, 0.1);">
|
||||||
|
<span class="fw-bold">{{ row.sla_base|floatformat:2 }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="background-color: rgba(253, 126, 20, 0.1);">
|
||||||
|
<span class="fw-bold">{{ row.units|floatformat:0 }} × {{ row.sla_per_unit|floatformat:4 }}</span><br>
|
||||||
|
<small class="text-muted">= {{ row.units|multiply:row.sla_per_unit|floatformat:2 }}</small>
|
||||||
|
</td>
|
||||||
|
<td class="text-center" style="background-color: rgba(220, 53, 69, 0.1);">
|
||||||
|
{% if row.mandatory_addons %}
|
||||||
|
{% for addon in row.mandatory_addons %}
|
||||||
|
<div class="mb-1">
|
||||||
|
{% if addon.addon_type == "Unit Rate" %}
|
||||||
|
<strong>{{ addon.name }}</strong><br>
|
||||||
|
<span class="fw-bold">{{ row.units|floatformat:0 }} × {{ addon.price|floatformat:4 }}</span><br>
|
||||||
|
<small class="text-muted">= {{ row.units|multiply:addon.price|floatformat:2 }}</small>
|
||||||
|
{% elif addon.addon_type == "Base Fee" %}
|
||||||
|
<strong>{{ addon.name }}</strong><br>
|
||||||
|
<span class="fw-bold">{{ addon.price|floatformat:2 }}</span>
|
||||||
|
{% else %}
|
||||||
|
<strong>{{ addon.name }}</strong><br>
|
||||||
|
<span class="fw-bold">{{ addon.price|floatformat:2 }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if not forloop.last %}<hr class="my-1">{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">n/a</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="text-center fw-bold" style="background-color: rgba(25, 135, 84, 0.2);">
|
||||||
|
{% with addon_total=row.mandatory_addons|calculate_addon_total:row.units %}
|
||||||
|
{{ row.sla_price|add_float:addon_total|floatformat:2 }}
|
||||||
|
{% endwith %}
|
||||||
|
</td>
|
||||||
{% if show_addon_details %}
|
{% if show_addon_details %}
|
||||||
<td>
|
<td>
|
||||||
{% if row.mandatory_addons or row.optional_addons %}
|
{% if row.mandatory_addons or row.optional_addons %}
|
||||||
|
@ -415,6 +584,7 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="text-muted">{{ row.term }}</td>
|
<td class="text-muted">{{ row.term }}</td>
|
||||||
<td class="text-muted">{{ comparison.currency }}</td>
|
<td class="text-muted">{{ comparison.currency }}</td>
|
||||||
|
<!-- Price breakdown columns for external comparisons -->
|
||||||
<td class="text-muted">-</td>
|
<td class="text-muted">-</td>
|
||||||
<td class="text-muted">-</td>
|
<td class="text-muted">-</td>
|
||||||
<td class="text-muted">-</td>
|
<td class="text-muted">-</td>
|
||||||
|
@ -462,7 +632,7 @@
|
||||||
|
|
||||||
{# Price Chart #}
|
{# Price Chart #}
|
||||||
<div class="price-chart mt-3">
|
<div class="price-chart mt-3">
|
||||||
<h5 class="text-muted">Price Chart - Units vs Final Price</h5>
|
<h5 class="text-muted">Price Breakdown Chart - Units vs Price Components</h5>
|
||||||
<div style="height: 400px;">
|
<div style="height: 400px;">
|
||||||
<canvas id="chart-{{ group_name|slugify }}-{{ service_level|slugify }}" width="400" height="200"></canvas>
|
<canvas id="chart-{{ group_name|slugify }}-{{ service_level|slugify }}" width="400" height="200"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
@ -517,9 +687,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
const priceComparisonCheckbox = document.getElementById('price_comparison');
|
const priceComparisonCheckbox = document.getElementById('price_comparison');
|
||||||
priceComparisonCheckbox.addEventListener('change', function() {
|
priceComparisonCheckbox.addEventListener('change', function() {
|
||||||
filterForm.submit();
|
filterForm.submit();
|
||||||
});
|
}); // Chart data for each service level
|
||||||
|
|
||||||
// Chart data for each service level
|
|
||||||
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
|
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
|
||||||
{% for service_level, pricing_data in service_levels.items %}
|
{% for service_level, pricing_data in service_levels.items %}
|
||||||
{% if pricing_data %}
|
{% if pricing_data %}
|
||||||
|
@ -536,18 +704,42 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
fill: false
|
fill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'SLA Price',
|
label: 'Compute Plan Price',
|
||||||
data: [{% for row in pricing_data %}{{ row.sla_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
data: [{% for row in pricing_data %}{{ row.compute_plan_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||||||
borderColor: 'rgb(255, 99, 132)',
|
borderColor: 'rgb(13, 110, 253)',
|
||||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
backgroundColor: 'rgba(13, 110, 253, 0.2)',
|
||||||
tension: 0.1,
|
tension: 0.1,
|
||||||
fill: false
|
fill: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Compute Plan Price',
|
label: 'SLA Base',
|
||||||
data: [{% for row in pricing_data %}{{ row.compute_plan_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
data: [{% for row in pricing_data %}{{ row.sla_base|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||||||
borderColor: 'rgb(54, 162, 235)',
|
borderColor: 'rgb(111, 66, 193)',
|
||||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
backgroundColor: 'rgba(111, 66, 193, 0.2)',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Units × SLA Per Unit',
|
||||||
|
data: [{% for row in pricing_data %}{{ row.units|multiply:row.sla_per_unit|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||||||
|
borderColor: 'rgb(253, 126, 20)',
|
||||||
|
backgroundColor: 'rgba(253, 126, 20, 0.2)',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Mandatory Add-ons',
|
||||||
|
data: [{% for row in pricing_data %}{{ row.mandatory_addons|calculate_addon_total:row.units|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||||||
|
borderColor: 'rgb(220, 53, 69)',
|
||||||
|
backgroundColor: 'rgba(220, 53, 69, 0.2)',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Total SLA Price',
|
||||||
|
data: [{% for row in pricing_data %}{{ row.sla_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||||||
|
borderColor: 'rgb(25, 135, 84)',
|
||||||
|
backgroundColor: 'rgba(25, 135, 84, 0.2)',
|
||||||
tension: 0.1,
|
tension: 0.1,
|
||||||
fill: false
|
fill: false
|
||||||
}
|
}
|
||||||
|
@ -580,7 +772,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: '{{ group_name }} - {{ service_level }} Pricing'
|
text: '{{ group_name }} - {{ service_level }} Price Breakdown'
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
display: true
|
display: true
|
||||||
|
|
45
hub/services/templatetags/math_tags.py
Normal file
45
hub/services/templatetags/math_tags.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="multiply")
|
||||||
|
def multiply(value, arg):
|
||||||
|
"""Multiply two numbers in Django templates"""
|
||||||
|
try:
|
||||||
|
return float(value) * float(arg)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="add_float")
|
||||||
|
def add_float(value, arg):
|
||||||
|
"""Add two numbers in Django templates"""
|
||||||
|
try:
|
||||||
|
return float(value) + float(arg)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="sum_addon_prices")
|
||||||
|
def sum_addon_prices(addons):
|
||||||
|
"""Sum the prices of addons"""
|
||||||
|
try:
|
||||||
|
return sum(addon.price for addon in addons)
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="calculate_addon_total")
|
||||||
|
def calculate_addon_total(addons, units):
|
||||||
|
"""Calculate total cost of addons based on their type and units"""
|
||||||
|
try:
|
||||||
|
total = 0
|
||||||
|
for addon in addons:
|
||||||
|
if addon.addon_type == "Unit Rate":
|
||||||
|
total += float(addon.price) * float(units)
|
||||||
|
else: # Base Fee or other types
|
||||||
|
total += float(addon.price)
|
||||||
|
return total
|
||||||
|
except (AttributeError, TypeError, ValueError):
|
||||||
|
return 0
|
Loading…
Add table
Add a link
Reference in a new issue