reworked price list view

This commit is contained in:
Tobias Brunner 2025-05-23 15:56:11 +02:00
parent d39ff91a74
commit c3d20fda7b
No known key found for this signature in database
3 changed files with 220 additions and 145 deletions

View file

@ -1,86 +1,174 @@
from django.shortcuts import render
from hub.services.models import ComputePlan, VSHNAppCatPrice, VSHNAppCatUnitRate
import re
from collections import defaultdict
from hub.services.models import ComputePlan, VSHNAppCatPrice
def natural_sort_key(name):
"""Extract numeric part from compute plan name for natural sorting"""
match = re.search(r"compute-std-(\d+)", name)
return int(match.group(1)) if match else 0
def pricelist(request):
# Get all compute plans and app catalog prices
# Get all active compute plans with their pricing data
compute_plans = (
ComputePlan.objects.all()
ComputePlan.objects.filter(active=True)
.select_related("cloud_provider")
.prefetch_related("prices")
.order_by("cloud_provider__name")
)
# Sort compute plans naturally by provider and plan number
compute_plans = sorted(
compute_plans, key=lambda x: (x.cloud_provider.name, natural_sort_key(x.name))
)
# Get all VSHNAppCat pricing configurations
appcat_prices = (
VSHNAppCatPrice.objects.all()
.select_related("service", "discount_model")
.prefetch_related("base_fees", "unit_rates")
.prefetch_related("base_fees", "unit_rates", "discount_model__tiers")
.order_by("service__name")
)
plans_data = []
pricing_data_by_service_level = defaultdict(list)
# Track processed combinations to avoid duplicates
processed_combinations = set()
for plan in compute_plans:
plan_data = {"plan": plan, "calculated_prices": []}
# Get all currencies available for this compute plan
plan_currencies = set(plan.prices.values_list("currency", flat=True))
for price_config in appcat_prices:
# Get all service levels for this price config
service_levels = (
VSHNAppCatUnitRate.objects.filter(vshn_appcat_price_config=price_config)
.values_list("service_level", flat=True)
.distinct()
)
# Determine number of units based on variable_unit
if price_config.variable_unit == VSHNAppCatPrice.VariableUnit.RAM:
for appcat_price in appcat_prices:
# Determine units based on variable unit type (RAM or CPU)
if appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.RAM:
units = int(plan.ram)
elif price_config.variable_unit == VSHNAppCatPrice.VariableUnit.CPU:
elif appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.CPU:
units = int(plan.vcpus)
else:
continue # Skip other unit type as we don't know yet how to handle them
continue
# Get all currencies used in base fees
currencies = price_config.base_fees.values_list(
"currency", flat=True
# Get currencies available for base fees and unit rates
base_fee_currencies = set(
appcat_price.base_fees.values_list("currency", flat=True)
)
# Get all distinct service levels for this pricing config
service_levels = appcat_price.unit_rates.values_list(
"service_level", flat=True
).distinct()
# Calculate prices for all combinations
for service_level in service_levels:
for currency in currencies:
final_price = price_config.calculate_final_price(
currency_code=currency,
service_level=service_level,
number_of_units=units,
# Get currencies available for this specific service level
unit_rate_currencies = set(
appcat_price.unit_rates.filter(
service_level=service_level
).values_list("currency", flat=True)
)
# Find currencies that exist in ALL three places: plan, base fee, and unit rate
matching_currencies = plan_currencies.intersection(
base_fee_currencies
).intersection(unit_rate_currencies)
# Skip if no common currencies found
if not matching_currencies:
continue
# Process each matching currency
for currency in matching_currencies:
# Create unique combination key to prevent duplicates
combination_key = (
plan.cloud_provider.name,
plan.name,
appcat_price.service.name,
service_level,
currency,
)
if final_price is not None:
service_level_display = dict(
VSHNAppCatPrice.ServiceLevel.choices
)[service_level]
# Skip if this combination was already processed
if combination_key in processed_combinations:
continue
# Include discount model information
discount_info = None
if (
price_config.discount_model
and price_config.discount_model.active
):
discount_info = {
"name": price_config.discount_model.name,
"description": price_config.discount_model.description,
}
processed_combinations.add(combination_key)
plan_data["calculated_prices"].append(
{
"service": price_config.service.name,
"variable_unit": price_config.get_variable_unit_display(),
"service_level": service_level_display,
"units": units,
"currency": currency,
"price": final_price,
"plan_term": plan.get_term_display(),
"service_term": price_config.get_term_display(),
"discount_model": discount_info,
}
# Get pricing data for this currency - skip if any is missing
compute_plan_price = plan.get_price(currency)
if compute_plan_price is None:
continue
base_fee = appcat_price.get_base_fee(currency)
if base_fee is None:
continue
unit_rate = appcat_price.get_unit_rate(currency, service_level)
if unit_rate is None:
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
# Apply discount model if available and active
if (
appcat_price.discount_model
and appcat_price.discount_model.active
):
discounted_price = (
appcat_price.discount_model.calculate_discount(
unit_rate, total_units
)
)
sla_price = base_fee + discounted_price
else:
# Standard pricing without discount
sla_price = base_fee + (total_units * unit_rate)
plans_data.append(plan_data)
# Calculate final price (compute + SLA)
final_price = compute_plan_price + sla_price
context = {"plans_data": plans_data}
# Get human-readable service level name
service_level_display = dict(VSHNAppCatPrice.ServiceLevel.choices)[
service_level
]
# Add row to the appropriate service level group
pricing_data_by_service_level[service_level_display].append(
{
"cloud_provider": plan.cloud_provider.name,
"service": appcat_price.service.name,
"compute_plan": plan.name,
"vcpus": plan.vcpus,
"ram": plan.ram,
"cpu_mem_ratio": plan.cpu_mem_ratio,
"term": plan.get_term_display(),
"currency": currency,
"compute_plan_price": compute_plan_price,
"variable_unit": appcat_price.get_variable_unit_display(),
"units": units,
"replica_enforce": replica_enforce,
"service_level": service_level_display,
"sla_base": base_fee,
"sla_per_unit": unit_rate,
"sla_price": sla_price,
"final_price": final_price,
"discount_model": (
appcat_price.discount_model.name
if appcat_price.discount_model
else None
),
"has_discount": bool(
appcat_price.discount_model
and appcat_price.discount_model.active
),
}
)
context = {"pricing_data_by_service_level": dict(pricing_data_by_service_level)}
return render(request, "services/pricelist.html", context)