implement plan pricing
This commit is contained in:
parent
96b667dd75
commit
61cabd1b1e
7 changed files with 192 additions and 1257 deletions
|
@ -242,221 +242,19 @@ def generate_exoscale_marketplace_yaml(offering):
|
|||
|
||||
|
||||
def generate_pricing_data(offering):
|
||||
"""Generate pricing data for a specific offering and cloud provider"""
|
||||
# Fetch compute plans for this cloud provider
|
||||
compute_plans = (
|
||||
ComputePlan.objects.filter(active=True, cloud_provider=offering.cloud_provider)
|
||||
.select_related("cloud_provider", "group")
|
||||
.prefetch_related("prices")
|
||||
.order_by("group__order", "group__name")
|
||||
)
|
||||
"""Generate pricing data for a specific offering and its plans with multi-currency support"""
|
||||
# Fetch all plans for this offering
|
||||
plans = offering.plans.prefetch_related("plan_prices")
|
||||
|
||||
# Apply natural sorting for compute plan names
|
||||
compute_plans = sorted(
|
||||
compute_plans,
|
||||
key=lambda x: (
|
||||
x.group.order if x.group else 999,
|
||||
x.group.name if x.group else "ZZZ",
|
||||
natural_sort_key(x.name),
|
||||
),
|
||||
)
|
||||
pricing_data = []
|
||||
for plan in plans:
|
||||
for plan_price in plan.plan_prices.all():
|
||||
pricing_data.append({
|
||||
"plan_id": plan.id,
|
||||
"plan_name": plan.name,
|
||||
"description": plan.description,
|
||||
"currency": plan_price.currency,
|
||||
"amount": float(plan_price.amount),
|
||||
})
|
||||
|
||||
# Fetch storage plans for this cloud provider
|
||||
storage_plans = (
|
||||
StoragePlan.objects.filter(cloud_provider=offering.cloud_provider)
|
||||
.prefetch_related("prices")
|
||||
.order_by("name")
|
||||
)
|
||||
|
||||
# Get default storage pricing (use first available storage plan)
|
||||
storage_price_data = {}
|
||||
if storage_plans.exists():
|
||||
default_storage_plan = storage_plans.first()
|
||||
for currency in ["CHF", "EUR", "USD"]: # Add currencies as needed
|
||||
price = default_storage_plan.get_price(currency)
|
||||
if price is not None:
|
||||
storage_price_data[currency] = price
|
||||
|
||||
# Fetch pricing for this specific service
|
||||
try:
|
||||
appcat_price = (
|
||||
VSHNAppCatPrice.objects.select_related("service", "discount_model")
|
||||
.prefetch_related("base_fees", "unit_rates", "discount_model__tiers")
|
||||
.get(service=offering.service)
|
||||
)
|
||||
except VSHNAppCatPrice.DoesNotExist:
|
||||
return None
|
||||
|
||||
pricing_data_by_group_and_service_level = defaultdict(lambda: defaultdict(list))
|
||||
processed_combinations = set()
|
||||
|
||||
# Generate pricing combinations for each compute plan
|
||||
for plan in compute_plans:
|
||||
plan_currencies = set(plan.prices.values_list("currency", flat=True))
|
||||
|
||||
# Determine units based on variable unit type
|
||||
if appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.RAM:
|
||||
units = int(plan.ram)
|
||||
elif appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.CPU:
|
||||
units = int(plan.vcpus)
|
||||
else:
|
||||
continue
|
||||
|
||||
base_fee_currencies = set(
|
||||
appcat_price.base_fees.values_list("currency", flat=True)
|
||||
)
|
||||
|
||||
service_levels = appcat_price.unit_rates.values_list(
|
||||
"service_level", flat=True
|
||||
).distinct()
|
||||
|
||||
for service_level in service_levels:
|
||||
unit_rate_currencies = set(
|
||||
appcat_price.unit_rates.filter(service_level=service_level).values_list(
|
||||
"currency", flat=True
|
||||
)
|
||||
)
|
||||
|
||||
# Find currencies that exist across all pricing components
|
||||
matching_currencies = plan_currencies.intersection(
|
||||
base_fee_currencies
|
||||
).intersection(unit_rate_currencies)
|
||||
|
||||
if not matching_currencies:
|
||||
continue
|
||||
|
||||
for currency in matching_currencies:
|
||||
combination_key = (
|
||||
plan.name,
|
||||
service_level,
|
||||
currency,
|
||||
)
|
||||
|
||||
# Skip if combination already processed
|
||||
if combination_key in processed_combinations:
|
||||
continue
|
||||
|
||||
processed_combinations.add(combination_key)
|
||||
|
||||
# Get pricing components
|
||||
compute_plan_price = plan.get_price(currency)
|
||||
base_fee = appcat_price.get_base_fee(currency, service_level)
|
||||
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 [compute_plan_price, base_fee, unit_rate]
|
||||
):
|
||||
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
|
||||
standard_sla_price = base_fee + (total_units * unit_rate)
|
||||
|
||||
# Apply discount if available
|
||||
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:
|
||||
sla_price = standard_sla_price
|
||||
|
||||
# Get addons information
|
||||
addons = appcat_price.addons.filter(active=True)
|
||||
mandatory_addons = []
|
||||
optional_addons = []
|
||||
|
||||
# Calculate additional price from mandatory addons
|
||||
addon_total = 0
|
||||
|
||||
for addon in addons:
|
||||
addon_price = None
|
||||
addon_price_per_unit = None
|
||||
|
||||
if addon.addon_type == "BF": # Base Fee
|
||||
addon_price = addon.get_price(currency, service_level)
|
||||
elif addon.addon_type == "UR": # Unit Rate
|
||||
addon_price_per_unit = addon.get_price(currency, service_level)
|
||||
if addon_price_per_unit:
|
||||
addon_price = addon_price_per_unit * total_units
|
||||
|
||||
addon_info = {
|
||||
"id": addon.id,
|
||||
"name": addon.name,
|
||||
"description": addon.description,
|
||||
"commercial_description": addon.commercial_description,
|
||||
"addon_type": addon.get_addon_type_display(),
|
||||
"price": addon_price,
|
||||
"price_per_unit": addon_price_per_unit, # Add per-unit price for frontend calculations
|
||||
}
|
||||
|
||||
if addon.mandatory:
|
||||
mandatory_addons.append(addon_info)
|
||||
if addon_price:
|
||||
addon_total += addon_price
|
||||
sla_price += addon_price
|
||||
else:
|
||||
optional_addons.append(addon_info)
|
||||
|
||||
final_price = compute_plan_price + sla_price
|
||||
service_level_display = dict(VSHNAppCatPrice.ServiceLevel.choices)[
|
||||
service_level
|
||||
]
|
||||
|
||||
group_name = plan.group.name if plan.group else "No Group"
|
||||
|
||||
# Add pricing data to the grouped structure
|
||||
pricing_data_by_group_and_service_level[group_name][
|
||||
service_level_display
|
||||
].append(
|
||||
{
|
||||
"compute_plan": plan.name,
|
||||
"compute_plan_group": group_name,
|
||||
"compute_plan_group_description": (
|
||||
plan.group.description if plan.group else ""
|
||||
),
|
||||
"vcpus": plan.vcpus,
|
||||
"ram": plan.ram,
|
||||
"currency": currency,
|
||||
"compute_plan_price": compute_plan_price,
|
||||
"sla_price": sla_price,
|
||||
"final_price": final_price,
|
||||
"storage_price": storage_price_data.get(currency, 0),
|
||||
"ha_replica_min": appcat_price.ha_replica_min,
|
||||
"ha_replica_max": appcat_price.ha_replica_max,
|
||||
"variable_unit": appcat_price.variable_unit,
|
||||
"units": units,
|
||||
"total_units": total_units,
|
||||
"mandatory_addons": mandatory_addons,
|
||||
"optional_addons": optional_addons,
|
||||
}
|
||||
)
|
||||
|
||||
# Order groups correctly, placing "No Group" last
|
||||
ordered_groups = {}
|
||||
all_group_names = list(pricing_data_by_group_and_service_level.keys())
|
||||
|
||||
if "No Group" in all_group_names:
|
||||
all_group_names.remove("No Group")
|
||||
all_group_names.append("No Group")
|
||||
|
||||
for group_name_key in all_group_names:
|
||||
ordered_groups[group_name_key] = pricing_data_by_group_and_service_level[
|
||||
group_name_key
|
||||
]
|
||||
|
||||
# Convert defaultdicts to regular dicts for the template
|
||||
final_context_data = {}
|
||||
for group_key, service_levels_dict in ordered_groups.items():
|
||||
final_context_data[group_key] = {
|
||||
sl_key: list(plans_list)
|
||||
for sl_key, plans_list in service_levels_dict.items()
|
||||
}
|
||||
|
||||
return final_context_data
|
||||
return {"plans": pricing_data}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue