794 lines
No EOL
52 KiB
HTML
794 lines
No EOL
52 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
{% load math_tags %}
|
||
|
||
{% block title %}Complete Price List{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script src="{% static "js/chart.js" %}"></script>
|
||
{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.addon-details {
|
||
max-width: 300px;
|
||
font-size: 0.85em;
|
||
}
|
||
|
||
.addon-item {
|
||
background-color: #f8f9fa;
|
||
border-radius: 4px;
|
||
padding: 4px 8px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.addon-name {
|
||
font-weight: 500;
|
||
}
|
||
|
||
.addon-price {
|
||
color: #28a745;
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.pricing-table th {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.final-price-header {
|
||
background-color: #28a745 !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.final-price-cell {
|
||
background-color: #d4edda;
|
||
color: #155724;
|
||
}
|
||
|
||
.comparison-row {
|
||
font-size: 0.9em;
|
||
}
|
||
|
||
.servala-row {
|
||
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>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid mt-4">
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<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 -->
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="mb-0">Filters</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<form method="get" class="row g-3" id="filter-form">
|
||
<div class="col-md-3">
|
||
<label for="cloud_provider" class="form-label">Cloud Provider</label>
|
||
<select name="cloud_provider" id="cloud_provider" class="form-select filter-select">
|
||
<option value="">All Providers</option>
|
||
{% for provider in all_cloud_providers %}
|
||
<option value="{{ provider }}" {% if provider == filter_cloud_provider %}selected{% endif %}>
|
||
{{ provider }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label for="service" class="form-label">Service</label>
|
||
<select name="service" id="service" class="form-select filter-select">
|
||
<option value="">All Services</option>
|
||
{% for service in all_services %}
|
||
<option value="{{ service }}" {% if service == filter_service %}selected{% endif %}>
|
||
{{ service }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label for="compute_plan_group" class="form-label">Compute Plan Group</label>
|
||
<select name="compute_plan_group" id="compute_plan_group" class="form-select filter-select">
|
||
<option value="">All Groups</option>
|
||
{% for group in all_compute_plan_groups %}
|
||
<option value="{{ group }}" {% if group == filter_compute_plan_group %}selected{% endif %}>
|
||
{{ group }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label for="service_level" class="form-label">Service Level</label>
|
||
<select name="service_level" id="service_level" class="form-select filter-select">
|
||
<option value="">All Service Levels</option>
|
||
{% for level in all_service_levels %}
|
||
<option value="{{ level }}" {% if level == filter_service_level %}selected{% endif %}>
|
||
{{ level }}
|
||
</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-12">
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="discount_details" value="true" id="discount_details" {% if show_discount_details %}checked{% endif %}>
|
||
<label class="form-check-label" for="discount_details">
|
||
Show discount details
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="addon_details" value="true" id="addon_details" {% if show_addon_details %}checked{% endif %}>
|
||
<label class="form-check-label" for="addon_details">
|
||
Show addon details
|
||
</label>
|
||
</div>
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="price_comparison" value="true" id="price_comparison" {% if show_price_comparison %}checked{% endif %}>
|
||
<label class="form-check-label" for="price_comparison">
|
||
Show external price comparisons
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="col-12">
|
||
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
||
<a href="{% url 'services:pricelist' %}" class="btn btn-secondary">Clear Filters</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Active Filters Display -->
|
||
{% if filter_cloud_provider or filter_service or filter_compute_plan_group or filter_service_level or show_discount_details or show_addon_details or show_price_comparison %}
|
||
<div class="alert alert-info">
|
||
<strong>Active Filters:</strong>
|
||
{% if filter_cloud_provider %}<span class="badge me-1">Cloud Provider: {{ filter_cloud_provider }}</span>{% endif %}
|
||
{% if filter_service %}<span class="badge me-1">Service: {{ filter_service }}</span>{% endif %}
|
||
{% if filter_compute_plan_group %}<span class="badge me-1">Group: {{ filter_compute_plan_group }}</span>{% endif %}
|
||
{% if filter_service_level %}<span class="badge me-1">Service Level: {{ filter_service_level }}</span>{% endif %}
|
||
{% if show_discount_details %}<span class="badge bg-secondary me-1">Discount Details</span>{% endif %}
|
||
{% if show_addon_details %}<span class="badge bg-info me-1">Addon Details</span>{% endif %}
|
||
{% if show_price_comparison %}<span class="badge bg-warning me-1">Price Comparison</span>{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if pricing_data_by_group_and_service_level %}
|
||
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
|
||
<div class="mb-5 border rounded p-3">
|
||
<h2 class="mb-3 text-primary">{{ group_name }}</h2>
|
||
|
||
{# Display group description and node_label from first available plan #}
|
||
{% for service_level, pricing_data in service_levels.items %}
|
||
{% if pricing_data and forloop.first %}
|
||
{% with pricing_data.0 as representative_plan %}
|
||
{% if representative_plan.compute_plan_group_description %}
|
||
<p class="text-muted mb-2"><strong>Description:</strong> {{ representative_plan.compute_plan_group_description }}</p>
|
||
{% endif %}
|
||
{% if representative_plan.compute_plan_group_node_label %}
|
||
<p class="text-muted mb-3"><strong>Node Label:</strong> <code>{{ representative_plan.compute_plan_group_node_label }}</code></p>
|
||
{% endif %}
|
||
|
||
{# Display storage pricing for this cloud provider #}
|
||
{% if representative_plan.storage_plans %}
|
||
<div class="mb-3">
|
||
<p class="text-muted mb-2"><strong>Storage Options:</strong></p>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-bordered">
|
||
<thead class="table-secondary">
|
||
<tr>
|
||
<th>Storage Plan</th>
|
||
<th>Term</th>
|
||
<th>Unit</th>
|
||
<th>Prices</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for storage_plan in representative_plan.storage_plans %}
|
||
<tr>
|
||
<td>{{ storage_plan.name }}</td>
|
||
<td>{{ storage_plan.get_term_display }}</td>
|
||
<td>{{ storage_plan.get_unit_display }}</td>
|
||
<td>
|
||
{% for price in storage_plan.prices.all %}
|
||
<span class="badge bg-light text-dark me-1">{{ price.amount }} {{ price.currency }}</span>
|
||
{% empty %}
|
||
<span class="text-muted">No prices</span>
|
||
{% endfor %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endwith %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
|
||
{% for service_level, pricing_data in service_levels.items %}
|
||
<div class="mb-4">
|
||
<h3 class="mb-3">SLA: {{ service_level }}</h3>
|
||
{% if pricing_data %}
|
||
{# Display common values for this service level #}
|
||
{% with pricing_data.0 as first_row %}
|
||
<div class="row mb-3">
|
||
<div class="col-md-2">
|
||
<strong>Cloud Provider:</strong> {{ first_row.cloud_provider }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>Service:</strong> {{ first_row.service }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>CPU/Memory Ratio:</strong> {{ first_row.cpu_mem_ratio }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>Variable Unit:</strong> {{ first_row.variable_unit }}
|
||
</div>
|
||
<div class="col-md-2">
|
||
<strong>Replica Enforce:</strong> {{ first_row.replica_enforce }}
|
||
</div>
|
||
</div>
|
||
|
||
{# Display add-on summary #}
|
||
{% if show_addon_details and first_row.mandatory_addons or first_row.optional_addons %}
|
||
<div class="card mb-3">
|
||
<div class="card-header">
|
||
<h6 class="mb-0">Available Add-ons for {{ first_row.service }}</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if first_row.mandatory_addons %}
|
||
<div class="mb-3">
|
||
<h6 class="text-success">Mandatory Add-ons (included in all plans):</h6>
|
||
<div class="row">
|
||
{% for addon in first_row.mandatory_addons %}
|
||
<div class="col-md-4 mb-2">
|
||
<div class="border border-success rounded p-2">
|
||
<strong>{{ addon.name }}</strong>
|
||
<div class="text-muted small">{{ addon.commercial_description|default:addon.description }}</div>
|
||
<div class="text-success fw-bold">{{ addon.price|floatformat:2 }} {{ first_row.currency }}</div>
|
||
<small class="text-muted">{{ addon.addon_type }}</small>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
{% if first_row.optional_addons %}
|
||
<div>
|
||
<h6 class="text-info">Optional Add-ons (can be added):</h6>
|
||
<div class="row">
|
||
{% for addon in first_row.optional_addons %}
|
||
<div class="col-md-4 mb-2">
|
||
<div class="border border-info rounded p-2">
|
||
<strong>{{ addon.name }}</strong>
|
||
<div class="text-muted small">{{ addon.commercial_description|default:addon.description }}</div>
|
||
<div class="text-info fw-bold">{{ addon.price|floatformat:2 }} {{ first_row.currency }}</div>
|
||
<small class="text-muted">{{ addon.addon_type }}</small>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% 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">
|
||
<table class="table table-striped table-bordered table-sm pricing-table">
|
||
<thead class="table-dark">
|
||
<tr>
|
||
<th rowspan="2">Compute Plan</th>
|
||
<th rowspan="2">Cloud Provider</th>
|
||
<th rowspan="2">vCPUs</th>
|
||
<th rowspan="2">RAM (GB)</th>
|
||
<th rowspan="2">Term</th>
|
||
<th rowspan="2">Currency</th>
|
||
<th colspan="5" class="text-center" style="background-color: #198754 !important;">Price Calculation Breakdown</th>
|
||
{% if show_addon_details %}
|
||
<th rowspan="2">Add-ons</th>
|
||
{% endif %}
|
||
{% if show_discount_details %}
|
||
<th rowspan="2">Discount Model</th>
|
||
<th rowspan="2">Discount Details</th>
|
||
{% endif %}
|
||
{% if show_price_comparison %}
|
||
<th rowspan="2">External Comparisons</th>
|
||
{% endif %}
|
||
<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>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in pricing_data %}
|
||
<tr class="servala-row">
|
||
<td>{{ row.compute_plan }}</td>
|
||
<td>{{ row.cloud_provider }}</td>
|
||
<td>{{ row.vcpus }}</td>
|
||
<td>{{ row.ram }}</td>
|
||
<td>{{ row.term }}</td>
|
||
<td>{{ row.currency }}</td>
|
||
<!-- Price Calculation Breakdown -->
|
||
<td class="text-center" style="background-color: rgba(13, 110, 253, 0.1);">
|
||
<span class="fw-bold">{{ row.compute_plan_price|floatformat:2 }}</span>
|
||
</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 %}
|
||
<td>
|
||
{% if row.mandatory_addons or row.optional_addons %}
|
||
<div class="addon-details">
|
||
{% if row.mandatory_addons %}
|
||
<div class="mb-2">
|
||
<small class="text-success fw-bold">Mandatory Add-ons:</small>
|
||
{% for addon in row.mandatory_addons %}
|
||
<div class="addon-item border-start border-success ps-2 mb-1">
|
||
<div class="d-flex justify-content-between">
|
||
<span class="addon-name">{{ addon.name }}</span>
|
||
<span class="addon-price fw-bold">{{ addon.price|floatformat:2 }} {{ row.currency }}</span>
|
||
</div>
|
||
{% if addon.commercial_description %}
|
||
<div class="text-muted small">{{ addon.commercial_description }}</div>
|
||
{% elif addon.description %}
|
||
<div class="text-muted small">{{ addon.description }}</div>
|
||
{% endif %}
|
||
<div class="text-muted small">Type: {{ addon.addon_type }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
{% if row.optional_addons %}
|
||
<div>
|
||
<small class="text-info fw-bold">Optional Add-ons:</small>
|
||
{% for addon in row.optional_addons %}
|
||
<div class="addon-item border-start border-info ps-2 mb-1">
|
||
<div class="d-flex justify-content-between">
|
||
<span class="addon-name">{{ addon.name }}</span>
|
||
<span class="addon-price">{{ addon.price|floatformat:2 }} {{ row.currency }}</span>
|
||
</div>
|
||
{% if addon.commercial_description %}
|
||
<div class="text-muted small">{{ addon.commercial_description }}</div>
|
||
{% elif addon.description %}
|
||
<div class="text-muted small">{{ addon.description }}</div>
|
||
{% endif %}
|
||
<div class="text-muted small">Type: {{ addon.addon_type }}</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% else %}
|
||
<span class="text-muted">No add-ons</span>
|
||
{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
{% if show_discount_details %}
|
||
<td>
|
||
{% if row.has_discount %}
|
||
{{ row.discount_model }}
|
||
{% else %}
|
||
None
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if row.has_discount %}
|
||
<small class="text-muted">
|
||
<strong>Total Units:</strong> {{ row.total_units }}<br>
|
||
<strong>Standard Price:</strong> {{ row.standard_sla_price|floatformat:2 }}<br>
|
||
<strong>Discounted Price:</strong> {{ row.discounted_sla_price|floatformat:2 }}<br>
|
||
<strong>Savings:</strong> {{ row.discount_savings|floatformat:2 }} ({{ row.discount_percentage|floatformat:1 }}%)<br>
|
||
{% if row.discount_breakdown %}
|
||
<strong>Breakdown:</strong><br>
|
||
{% for tier in row.discount_breakdown %}
|
||
{{ tier.tier_range }} units: {{ tier.units }} × {{ tier.rate|floatformat:4 }} = {{ tier.subtotal|floatformat:2 }}<br>
|
||
{% endfor %}
|
||
{% endif %}
|
||
</small>
|
||
{% else %}
|
||
<small class="text-muted">No discount applied</small>
|
||
{% endif %}
|
||
</td>
|
||
{% endif %}
|
||
{% if show_price_comparison %}
|
||
<td>
|
||
<span class="badge">-</span>
|
||
</td>
|
||
{% endif %}
|
||
<td class="final-price-cell fw-bold">{{ row.final_price|floatformat:2 }}</td>
|
||
</tr>
|
||
{% if show_price_comparison and row.external_comparisons %}
|
||
{% for comparison in row.external_comparisons %}
|
||
<tr class="table-light comparison-row">
|
||
<td class="text-muted">{{ comparison.plan_name }}</td>
|
||
<td class="text-muted">{{ comparison.provider }}</td>
|
||
<td class="text-muted">
|
||
{% if comparison.vcpus %}{{ comparison.vcpus }}{% else %}-{% endif %}
|
||
</td>
|
||
<td class="text-muted">
|
||
{% if comparison.ram %}{{ comparison.ram }}{% else %}-{% endif %}
|
||
</td>
|
||
<td class="text-muted">{{ row.term }}</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>
|
||
{% if show_addon_details %}
|
||
<td class="text-muted">-</td>
|
||
{% endif %}
|
||
{% if show_discount_details %}
|
||
<td class="text-muted">-</td>
|
||
<td class="text-muted">-</td>
|
||
{% endif %}
|
||
<td>
|
||
<small>
|
||
<span class="badge bg-secondary">{% if comparison.source %}<span class="text-muted"><a href="{{ comparison.source }}" target="_blank">{{ comparison.provider }}</a></span>{% else %}{{ comparison.provider }}{% endif %}</span><br>
|
||
{% if comparison.description %}
|
||
<span class="text-muted">{{ comparison.description }}</span><br>
|
||
{% endif %}
|
||
{% if comparison.storage %}
|
||
<span class="text-muted">Storage: {{ comparison.storage }} GB</span><br>
|
||
{% endif %}
|
||
{% if comparison.replicas %}
|
||
<span class="text-muted">Replicas: {{ comparison.replicas }}</span><br>
|
||
{% endif %}
|
||
{% if comparison.ratio %}
|
||
<span class="text-muted">Price ratio: {{ comparison.ratio|floatformat:2 }}x</span><br>
|
||
{% endif %}
|
||
</small>
|
||
</td>
|
||
<td class="fw-bold">
|
||
{{ comparison.amount|floatformat:2 }} {{ comparison.currency }}
|
||
{% if comparison.difference > 0 %}
|
||
<span class="badge bg-success ms-1">+{{ comparison.difference|floatformat:2 }}</span>
|
||
{% elif comparison.difference < 0 %}
|
||
<span class="badge bg-danger ms-1">{{ comparison.difference|floatformat:2 }}</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
{% endif %}
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
{# Price Chart #}
|
||
<div class="price-chart mt-3">
|
||
<h5 class="text-muted">Price Breakdown Chart - Units vs Price Components</h5>
|
||
<div style="height: 400px;">
|
||
<canvas id="chart-{{ group_name|slugify }}-{{ service_level|slugify }}" width="400" height="200"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<p class="text-muted"><strong>{{ pricing_data|length }}</strong> variants for {{ service_level }} in {{ group_name }}</p>
|
||
{% else %}
|
||
<p class="text-muted">No pricing variants available for {{ service_level }} in {{ group_name }}.</p>
|
||
{% endif %}
|
||
</div>
|
||
{% empty %}
|
||
<p class="text-muted">No service levels with pricing data found for group: {{ group_name }}.</p>
|
||
{% endfor %}
|
||
</div>
|
||
{% endfor %}
|
||
{% else %}
|
||
<div class="alert alert-info">
|
||
<h4>No pricing data available</h4>
|
||
<p>{% if filter_cloud_provider or filter_service or filter_compute_plan_group or filter_service_level %}No data matches the selected filters. Try adjusting your filter criteria.{% else %}Please ensure you have active compute plans with prices and VSHNAppCat price configurations.{% endif %}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Auto-submit form when filter dropdowns change
|
||
const filterForm = document.getElementById('filter-form');
|
||
const filterSelects = document.querySelectorAll('.filter-select');
|
||
const discountCheckbox = document.getElementById('discount_details');
|
||
const addonCheckbox = document.getElementById('addon_details');
|
||
|
||
// Add change event listeners to all filter dropdowns
|
||
filterSelects.forEach(function(select) {
|
||
select.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
});
|
||
});
|
||
|
||
// Add change event listener to discount details checkbox
|
||
discountCheckbox.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
});
|
||
|
||
// Add change event listener to addon details checkbox
|
||
addonCheckbox.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
});
|
||
|
||
// Add change event listener to price comparison checkbox
|
||
const priceComparisonCheckbox = document.getElementById('price_comparison');
|
||
priceComparisonCheckbox.addEventListener('change', function() {
|
||
filterForm.submit();
|
||
}); // Chart data for each service level
|
||
{% for group_name, service_levels in pricing_data_by_group_and_service_level.items %}
|
||
{% for service_level, pricing_data in service_levels.items %}
|
||
{% if pricing_data %}
|
||
// Prepare data for {{ group_name }} - {{ service_level }}
|
||
const chartData{{ forloop.parentloop.counter }}{{ forloop.counter }} = {
|
||
labels: [{% for row in pricing_data %}{{ row.units }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
datasets: [
|
||
{
|
||
label: 'Final Price',
|
||
data: [{% for row in pricing_data %}{{ row.final_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
borderColor: 'rgb(75, 192, 192)',
|
||
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
||
tension: 0.1,
|
||
fill: false
|
||
},
|
||
{
|
||
label: 'Compute Plan Price',
|
||
data: [{% for row in pricing_data %}{{ row.compute_plan_price|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
borderColor: 'rgb(13, 110, 253)',
|
||
backgroundColor: 'rgba(13, 110, 253, 0.2)',
|
||
tension: 0.1,
|
||
fill: false
|
||
},
|
||
{
|
||
label: 'SLA Base',
|
||
data: [{% for row in pricing_data %}{{ row.sla_base|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
|
||
borderColor: 'rgb(111, 66, 193)',
|
||
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,
|
||
fill: false
|
||
}
|
||
]
|
||
};
|
||
|
||
// Create chart for {{ group_name }} - {{ service_level }}
|
||
const ctx{{ forloop.parentloop.counter }}{{ forloop.counter }} = document.getElementById('chart-{{ group_name|slugify }}-{{ service_level|slugify }}').getContext('2d');
|
||
new Chart(ctx{{ forloop.parentloop.counter }}{{ forloop.counter }}, {
|
||
type: 'line',
|
||
data: chartData{{ forloop.parentloop.counter }}{{ forloop.counter }},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
scales: {
|
||
x: {
|
||
title: {
|
||
display: true,
|
||
text: 'Units'
|
||
}
|
||
},
|
||
y: {
|
||
title: {
|
||
display: true,
|
||
text: 'Price ({{ pricing_data.0.currency|default:"CHF" }})'
|
||
},
|
||
beginAtZero: true
|
||
}
|
||
},
|
||
plugins: {
|
||
title: {
|
||
display: true,
|
||
text: '{{ group_name }} - {{ service_level }} Price Breakdown'
|
||
},
|
||
legend: {
|
||
display: true
|
||
}
|
||
},
|
||
elements: {
|
||
point: {
|
||
radius: 4,
|
||
hoverRadius: 6
|
||
}
|
||
}
|
||
}
|
||
});
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% endfor %}
|
||
});
|
||
</script>
|
||
{% endblock %} |