From 15869ca542840125cc694c57c4411d18ac2b5715 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Fri, 20 Jun 2025 14:20:28 +0200 Subject: [PATCH] pricelist internal plans comparison --- .../templates/services/pricelist.html | 152 ++++++++++++++++-- hub/services/views/pricelist.py | 123 ++++++++++++++ 2 files changed, 260 insertions(+), 15 deletions(-) diff --git a/hub/services/templates/services/pricelist.html b/hub/services/templates/services/pricelist.html index bc90f6d..502b243 100644 --- a/hub/services/templates/services/pricelist.html +++ b/hub/services/templates/services/pricelist.html @@ -51,6 +51,41 @@ .servala-row { border-bottom: 2px solid #007bff; + border-left: 4px solid #007bff; + background-color: rgba(13, 110, 253, 0.03); + } + + .internal-comparison-row { + background-color: rgba(25, 135, 84, 0.08) !important; + border-left: 4px solid #198754; + border-top: 1px solid #198754; + } + + .external-comparison-row { + background-color: rgba(108, 117, 125, 0.08) !important; + border-left: 4px solid #6c757d; + border-top: 1px solid #6c757d; + } + + /* Group comparison rows visually */ + .comparison-group { + border-bottom: 2px solid #dee2e6; + margin-bottom: 8px; + } + + /* Add visual connection between main row and comparisons */ + .has-comparisons { + position: relative; + } + + .has-comparisons::after { + content: ''; + position: absolute; + left: 0; + bottom: -1px; + width: 100%; + height: 2px; + background: linear-gradient(to right, #007bff, transparent); } /* Price calculation breakdown styling */ @@ -171,6 +206,10 @@ 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. +

+ Price Comparisons: When enabled, you'll see: +
External Providers - Competitor prices from AWS, Google Cloud, etc. +
Other Servala Providers - Same service specs on different cloud providers within our network

@@ -246,7 +285,7 @@
@@ -268,7 +307,7 @@ {% if filter_service_level %}Service Level: {{ filter_service_level }}{% endif %} {% if show_discount_details %}Discount Details{% endif %} {% if show_addon_details %}Addon Details{% endif %} - {% if show_price_comparison %}Price Comparison{% endif %} + {% if show_price_comparison %}Price Comparisons{% endif %} {% endif %} @@ -430,7 +469,7 @@ Discount Details {% endif %} {% if show_price_comparison %} - External Comparisons + Price Comparisons {% endif %} Final Price @@ -444,7 +483,7 @@ {% for row in pricing_data %} - + {{ row.compute_plan }} {{ row.cloud_provider }} {{ row.vcpus }} @@ -566,16 +605,39 @@ {% endif %} {% if show_price_comparison %} - - + {% if row.external_comparisons or row.internal_comparisons %} +
+ {% if row.external_comparisons %} +
+ {{ row.external_comparisons|length }} External +
+ {% endif %} + {% if row.internal_comparisons %} +
+ {{ row.internal_comparisons|length }} Internal +
+ {% endif %} + See rows below +
+ {% else %} + No comparisons + {% endif %} {% endif %} {{ row.final_price|floatformat:2 }} {% if show_price_comparison and row.external_comparisons %} {% for comparison in row.external_comparisons %} - - {{ comparison.plan_name }} - {{ comparison.provider }} + + +
+ + {{ comparison.plan_name }} +
+ + + {{ comparison.provider }} + {% if comparison.vcpus %}{{ comparison.vcpus }}{% else %}-{% endif %} @@ -585,11 +647,11 @@ {{ row.term }} {{ comparison.currency }} - - - - - - - - - - + - + - + - + - + - {% if show_addon_details %} - {% endif %} @@ -599,7 +661,11 @@ {% endif %} - {% if comparison.source %}{{ comparison.provider }}{% else %}{{ comparison.provider }}{% endif %}
+ {% if comparison.source %} + + External Source +
+ {% endif %} {% if comparison.description %} {{ comparison.description }}
{% endif %} @@ -610,7 +676,7 @@ Replicas: {{ comparison.replicas }}
{% endif %} {% if comparison.ratio %} - Price ratio: {{ comparison.ratio|floatformat:2 }}x
+ Ratio: {{ comparison.ratio|floatformat:2 }}x {% endif %}
@@ -625,6 +691,62 @@ {% endfor %} {% endif %} + {% if show_price_comparison and row.internal_comparisons %} + {% for comparison in row.internal_comparisons %} + + +
+ + {{ comparison.plan_name }} +
+ + + {{ comparison.provider }} + + {{ comparison.vcpus }} + {{ comparison.ram }} + {{ row.term }} + {{ comparison.currency }} + + {{ comparison.compute_plan_price|floatformat:2 }} + Same + Same + Same + {{ comparison.service_price|floatformat:2 }} + {% if show_addon_details %} + Same as above + {% endif %} + {% if show_discount_details %} + Same + Same + {% endif %} + + + Servala Network
+ {{ comparison.description }}
+ Group: {{ comparison.group_name }}
+ Ratio: {{ comparison.ratio|floatformat:2 }}x +
+ + + {{ comparison.amount|floatformat:2 }} {{ comparison.currency }} + {% if comparison.difference > 0 %} + +{{ comparison.difference|floatformat:2 }} + {% elif comparison.difference < 0 %} + {{ comparison.difference|floatformat:2 }} + {% elif comparison.difference == 0 %} + Same + {% endif %} + + + {% endfor %} + {% endif %} + + {% if show_price_comparison and row.external_comparisons or row.internal_comparisons %} + + + + {% endif %} {% endfor %} diff --git a/hub/services/views/pricelist.py b/hub/services/views/pricelist.py index b392f6b..cc3996a 100644 --- a/hub/services/views/pricelist.py +++ b/hub/services/views/pricelist.py @@ -38,6 +38,90 @@ def get_external_price_comparisons(plan, appcat_price, currency, service_level): return [] +def get_internal_cloud_provider_comparisons( + plan, appcat_price, currency, service_level +): + """Get internal comparisons with other cloud provider plans from the database""" + try: + # Get similar compute plans from other cloud providers with same specs + similar_plans = ( + ComputePlan.objects.filter( + active=True, + vcpus=plan.vcpus, + ram=plan.ram, + ) + .exclude(cloud_provider=plan.cloud_provider) # Exclude same cloud provider + .select_related("cloud_provider") + .prefetch_related("prices") + ) + + internal_comparisons = [] + + for similar_plan in similar_plans: + # Get pricing components for comparison plan + compare_plan_price = similar_plan.get_price(currency) + compare_base_fee = appcat_price.get_base_fee(currency) + compare_unit_rate = appcat_price.get_unit_rate(currency, service_level) + + # Skip if any pricing component is missing + if any( + price is None + for price in [compare_plan_price, compare_base_fee, compare_unit_rate] + ): + continue + + # Calculate units based on variable unit type + if appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.RAM: + units = int(similar_plan.ram) + elif appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.CPU: + units = int(similar_plan.vcpus) + else: + continue + + # Calculate replica enforcement based on service level + if service_level == VSHNAppCatPrice.ServiceLevel.GUARANTEED: + replica_enforce = appcat_price.ha_replica_min + else: + replica_enforce = 1 + + total_units = units * replica_enforce + + # Calculate final price using the same logic as the main plan + price_calculation = appcat_price.calculate_final_price( + currency_code=currency, + service_level=service_level, + number_of_units=total_units, + addon_ids=None, # Include only mandatory addons + ) + + if price_calculation is None: + continue + + service_price_with_addons = price_calculation["total_price"] + compare_final_price = compare_plan_price + service_price_with_addons + + internal_comparisons.append( + { + "plan_name": similar_plan.name, + "provider": similar_plan.cloud_provider.name, + "compute_plan_price": compare_plan_price, + "service_price": service_price_with_addons, + "final_price": compare_final_price, + "currency": currency, + "vcpus": similar_plan.vcpus, + "ram": similar_plan.ram, + "group_name": ( + similar_plan.group.name if similar_plan.group else "No Group" + ), + "is_internal": True, # Flag to distinguish from external comparisons + } + ) + + return internal_comparisons + except Exception: + return [] + + @staff_member_required def pricelist(request): """Generate comprehensive price list grouped by compute plan groups and service levels""" @@ -287,7 +371,9 @@ def pricelist(request): # Get external price comparisons if enabled external_comparisons = [] + internal_comparisons = [] if show_price_comparison: + # Get external price comparisons external_prices = get_external_price_comparisons( plan, appcat_price, currency, service_level ) @@ -313,6 +399,42 @@ def pricelist(request): "ratio": ratio, "source": ext_price.source, "date_retrieved": ext_price.date_retrieved, + "is_internal": False, + } + ) + + # Get internal cloud provider comparisons + internal_price_comparisons = ( + get_internal_cloud_provider_comparisons( + plan, appcat_price, currency, service_level + ) + ) + for int_price in internal_price_comparisons: + # Calculate price difference + difference = int_price["final_price"] - final_price + ratio = ( + int_price["final_price"] / final_price + if final_price > 0 + else 0 + ) + + internal_comparisons.append( + { + "plan_name": int_price["plan_name"], + "provider": int_price["provider"], + "description": f"Same specs with {int_price['provider']}", + "amount": int_price["final_price"], + "currency": int_price["currency"], + "vcpus": int_price["vcpus"], + "ram": int_price["ram"], + "group_name": int_price["group_name"], + "compute_plan_price": int_price[ + "compute_plan_price" + ], + "service_price": int_price["service_price"], + "difference": difference, + "ratio": ratio, + "is_internal": True, } ) @@ -374,6 +496,7 @@ def pricelist(request): and appcat_price.discount_model.active ), "external_comparisons": external_comparisons, + "internal_comparisons": internal_comparisons, "mandatory_addons": mandatory_addons, "optional_addons": optional_addons, }