service level specific base fees
This commit is contained in:
parent
150250bfb1
commit
033eea92cd
9 changed files with 716 additions and 58 deletions
|
@ -289,7 +289,7 @@ class VSHNAppCatBaseFeeInline(admin.TabularInline):
|
|||
|
||||
model = VSHNAppCatBaseFee
|
||||
extra = 1
|
||||
fields = ("currency", "amount")
|
||||
fields = ("currency", "service_level", "amount")
|
||||
|
||||
|
||||
class VSHNAppCatUnitRateInline(admin.TabularInline):
|
||||
|
@ -350,7 +350,7 @@ class VSHNAppCatPriceAdmin(admin.ModelAdmin):
|
|||
if not fees:
|
||||
return "No base fees"
|
||||
return format_html(
|
||||
"<br>".join([f"{fee.amount} {fee.currency}" for fee in fees])
|
||||
"<br>".join([f"{fee.amount} {fee.currency} ({fee.get_service_level_display()})" for fee in fees])
|
||||
)
|
||||
|
||||
admin_display_base_fees.short_description = "Base Fees"
|
||||
|
@ -561,7 +561,7 @@ class VSHNAppCatAddonBaseFeeInline(admin.TabularInline):
|
|||
|
||||
model = VSHNAppCatAddonBaseFee
|
||||
extra = 1
|
||||
fields = ("currency", "amount")
|
||||
fields = ("currency", "service_level", "amount")
|
||||
|
||||
|
||||
class VSHNAppCatAddonUnitRateInline(admin.TabularInline):
|
||||
|
@ -618,7 +618,7 @@ class VSHNAppCatAddonAdmin(admin.ModelAdmin):
|
|||
if not fees:
|
||||
return "No base fees set"
|
||||
return format_html(
|
||||
"<br>".join([f"{fee.amount} {fee.currency}" for fee in fees])
|
||||
"<br>".join([f"{fee.amount} {fee.currency} ({fee.get_service_level_display()})" for fee in fees])
|
||||
)
|
||||
elif obj.addon_type == "UR": # Unit Rate
|
||||
rates = obj.unit_rates.all()
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# Generated by Django 5.2 on 2025-06-20 13:33
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("services", "0035_alter_article_image_vshnappcataddon_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="vshnappcataddonbasefee",
|
||||
options={
|
||||
"ordering": ["currency", "service_level"],
|
||||
"verbose_name": "Addon Base Fee",
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name="vshnappcatbasefee",
|
||||
options={
|
||||
"ordering": ["currency", "service_level"],
|
||||
"verbose_name": "Service Base Fee",
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="vshnappcataddonbasefee",
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="vshnappcatbasefee",
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="vshnappcataddonbasefee",
|
||||
name="service_level",
|
||||
field=models.CharField(
|
||||
choices=[("BE", "Best Effort"), ("GA", "Guaranteed Availability")],
|
||||
default="BE",
|
||||
max_length=2,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="vshnappcatbasefee",
|
||||
name="service_level",
|
||||
field=models.CharField(
|
||||
choices=[("BE", "Best Effort"), ("GA", "Guaranteed Availability")],
|
||||
default="BE",
|
||||
max_length=2,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="vshnappcataddonbasefee",
|
||||
name="amount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
help_text="Base fee in the specified currency and service level, excl. VAT",
|
||||
max_digits=10,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="vshnappcatbasefee",
|
||||
name="amount",
|
||||
field=models.DecimalField(
|
||||
decimal_places=2,
|
||||
help_text="Base fee in the specified currency and service level, excl. VAT",
|
||||
max_digits=10,
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="vshnappcataddonbasefee",
|
||||
unique_together={("addon", "currency", "service_level")},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name="vshnappcatbasefee",
|
||||
unique_together={("vshn_appcat_price_config", "currency", "service_level")},
|
||||
),
|
||||
]
|
|
@ -250,29 +250,6 @@ class DiscountTier(models.Model):
|
|||
return f"{self.discount_model.name}: {self.min_units}+ units → {self.discount_percent}% discount"
|
||||
|
||||
|
||||
class VSHNAppCatBaseFee(models.Model):
|
||||
vshn_appcat_price_config = models.ForeignKey(
|
||||
"VSHNAppCatPrice", on_delete=models.CASCADE, related_name="base_fees"
|
||||
)
|
||||
currency = models.CharField(
|
||||
max_length=3,
|
||||
choices=Currency.choices,
|
||||
)
|
||||
amount = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
help_text="Base fee in the specified currency, excl. VAT",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Service Base Fee"
|
||||
unique_together = ("vshn_appcat_price_config", "currency")
|
||||
ordering = ["currency"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.vshn_appcat_price_config.service.name} Base Fee - {self.amount} {self.currency}"
|
||||
|
||||
|
||||
class VSHNAppCatPrice(models.Model):
|
||||
class VariableUnit(models.TextChoices):
|
||||
RAM = "RAM", "Memory (RAM)"
|
||||
|
@ -325,12 +302,6 @@ class VSHNAppCatPrice(models.Model):
|
|||
def __str__(self):
|
||||
return f"{self.service.name} - {self.get_variable_unit_display()} based pricing"
|
||||
|
||||
def get_base_fee(self, currency_code: str):
|
||||
try:
|
||||
return self.base_fees.get(currency=currency_code).amount
|
||||
except VSHNAppCatBaseFee.DoesNotExist:
|
||||
return None
|
||||
|
||||
def get_unit_rate(self, currency_code: str, service_level: str):
|
||||
try:
|
||||
return self.unit_rates.get(
|
||||
|
@ -346,7 +317,7 @@ class VSHNAppCatPrice(models.Model):
|
|||
number_of_units: int,
|
||||
addon_ids=None,
|
||||
):
|
||||
base_fee = self.get_base_fee(currency_code)
|
||||
base_fee = self.get_base_fee(currency_code, service_level)
|
||||
unit_rate = self.get_unit_rate(currency_code, service_level)
|
||||
|
||||
if base_fee is None or unit_rate is None:
|
||||
|
@ -380,7 +351,7 @@ class VSHNAppCatPrice(models.Model):
|
|||
for addon in addons:
|
||||
addon_price = 0
|
||||
if addon.addon_type == VSHNAppCatAddon.AddonType.BASE_FEE:
|
||||
addon_price_value = addon.get_price(currency_code)
|
||||
addon_price_value = addon.get_price(currency_code, service_level)
|
||||
if addon_price_value:
|
||||
addon_price = addon_price_value
|
||||
elif addon.addon_type == VSHNAppCatAddon.AddonType.UNIT_RATE:
|
||||
|
@ -408,6 +379,15 @@ class VSHNAppCatPrice(models.Model):
|
|||
"addon_breakdown": addon_breakdown,
|
||||
}
|
||||
|
||||
def get_base_fee(self, currency_code: str, service_level: str):
|
||||
"""
|
||||
Get the base fee for the given currency and service level.
|
||||
"""
|
||||
try:
|
||||
return self.base_fees.get(currency=currency_code, service_level=service_level).amount
|
||||
except VSHNAppCatBaseFee.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class VSHNAppCatUnitRate(models.Model):
|
||||
vshn_appcat_price_config = models.ForeignKey(
|
||||
|
@ -477,10 +457,16 @@ class VSHNAppCatAddon(models.Model):
|
|||
return f"{self.vshn_appcat_price_config.service.name} - {self.name}"
|
||||
|
||||
def get_price(self, currency_code: str, service_level: str = None):
|
||||
"""Get the price for this addon in the specified currency and service level"""
|
||||
"""
|
||||
Get the price for this addon in the specified currency and service level.
|
||||
For base fee addons, service_level is required and used.
|
||||
For unit rate addons, service_level is required and used.
|
||||
"""
|
||||
try:
|
||||
if self.addon_type == self.AddonType.BASE_FEE:
|
||||
return self.base_fees.get(currency=currency_code).amount
|
||||
if not service_level:
|
||||
raise ValueError("Service level is required for base fee addons")
|
||||
return self.base_fees.get(currency=currency_code, service_level=service_level).amount
|
||||
elif self.addon_type == self.AddonType.UNIT_RATE:
|
||||
if not service_level:
|
||||
raise ValueError("Service level is required for unit rate addons")
|
||||
|
@ -495,8 +481,9 @@ class VSHNAppCatAddon(models.Model):
|
|||
|
||||
|
||||
class VSHNAppCatAddonBaseFee(models.Model):
|
||||
"""Base fee for an addon (fixed amount regardless of units)"""
|
||||
|
||||
"""
|
||||
Base fee for an addon (fixed amount regardless of units), specified per currency and service level.
|
||||
"""
|
||||
addon = models.ForeignKey(
|
||||
VSHNAppCatAddon, on_delete=models.CASCADE, related_name="base_fees"
|
||||
)
|
||||
|
@ -504,19 +491,27 @@ class VSHNAppCatAddonBaseFee(models.Model):
|
|||
max_length=3,
|
||||
choices=Currency.choices,
|
||||
)
|
||||
service_level = models.CharField(
|
||||
max_length=2,
|
||||
choices=VSHNAppCatPrice.ServiceLevel.choices,
|
||||
default=VSHNAppCatPrice.ServiceLevel.BEST_EFFORT,
|
||||
)
|
||||
amount = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
help_text="Base fee in the specified currency, excl. VAT",
|
||||
help_text="Base fee in the specified currency and service level, excl. VAT",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Addon Base Fee"
|
||||
unique_together = ("addon", "currency")
|
||||
ordering = ["currency"]
|
||||
unique_together = ("addon", "currency", "service_level")
|
||||
ordering = ["currency", "service_level"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.addon.name} Base Fee - {self.amount} {self.currency}"
|
||||
return f"{self.addon.name} Base Fee - {self.amount} {self.currency} ({self.get_service_level_display()})"
|
||||
|
||||
def get_service_level_display(self):
|
||||
return dict(VSHNAppCatPrice.ServiceLevel.choices).get(self.service_level, self.service_level)
|
||||
|
||||
|
||||
class VSHNAppCatAddonUnitRate(models.Model):
|
||||
|
@ -548,6 +543,40 @@ class VSHNAppCatAddonUnitRate(models.Model):
|
|||
return f"{self.addon.name} - {self.get_service_level_display()} Unit Rate - {self.amount} {self.currency}"
|
||||
|
||||
|
||||
class VSHNAppCatBaseFee(models.Model):
|
||||
"""
|
||||
Base fee for a service, specified per currency and service level.
|
||||
"""
|
||||
vshn_appcat_price_config = models.ForeignKey(
|
||||
"VSHNAppCatPrice", on_delete=models.CASCADE, related_name="base_fees"
|
||||
)
|
||||
currency = models.CharField(
|
||||
max_length=3,
|
||||
choices=Currency.choices,
|
||||
)
|
||||
service_level = models.CharField(
|
||||
max_length=2,
|
||||
choices=VSHNAppCatPrice.ServiceLevel.choices,
|
||||
default=VSHNAppCatPrice.ServiceLevel.BEST_EFFORT,
|
||||
)
|
||||
amount = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
help_text="Base fee in the specified currency and service level, excl. VAT",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Service Base Fee"
|
||||
unique_together = ("vshn_appcat_price_config", "currency", "service_level")
|
||||
ordering = ["currency", "service_level"]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.vshn_appcat_price_config.service.name} Base Fee - {self.amount} {self.currency} ({self.get_service_level_display()})"
|
||||
|
||||
def get_service_level_display(self):
|
||||
return dict(VSHNAppCatPrice.ServiceLevel.choices).get(self.service_level, self.service_level)
|
||||
|
||||
|
||||
class ExternalPricePlans(models.Model):
|
||||
plan_name = models.CharField()
|
||||
description = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
|
|
@ -303,14 +303,15 @@ class VSHNAppCatPriceTestCase(TestCase):
|
|||
base_fee = VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
retrieved_fee = self.price_config.get_base_fee(Currency.CHF)
|
||||
retrieved_fee = self.price_config.get_base_fee(Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED)
|
||||
self.assertEqual(retrieved_fee, Decimal("50.00"))
|
||||
|
||||
# Test non-existent currency
|
||||
non_existent_fee = self.price_config.get_base_fee(Currency.EUR)
|
||||
non_existent_fee = self.price_config.get_base_fee(Currency.EUR, VSHNAppCatPrice.ServiceLevel.GUARANTEED)
|
||||
self.assertIsNone(non_existent_fee)
|
||||
|
||||
def test_unit_rate_creation_and_retrieval(self):
|
||||
|
@ -339,6 +340,7 @@ class VSHNAppCatPriceTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -364,6 +366,7 @@ class VSHNAppCatPriceTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -392,6 +395,7 @@ class VSHNAppCatPriceTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -413,6 +417,7 @@ class VSHNAppCatPriceTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -431,6 +436,7 @@ class VSHNAppCatPriceTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -473,6 +479,7 @@ class VSHNAppCatAddonTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=self.price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -495,11 +502,11 @@ class VSHNAppCatAddonTestCase(TestCase):
|
|||
|
||||
# Create base fee for addon
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=addon, currency=Currency.CHF, amount=Decimal("25.00")
|
||||
addon=addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("25.00")
|
||||
)
|
||||
|
||||
# Test get_price method
|
||||
price = addon.get_price(Currency.CHF)
|
||||
price = addon.get_price(Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED)
|
||||
self.assertEqual(price, Decimal("25.00"))
|
||||
|
||||
def test_addon_unit_rate_type(self):
|
||||
|
@ -553,7 +560,7 @@ class VSHNAppCatAddonTestCase(TestCase):
|
|||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=mandatory_addon, currency=Currency.CHF, amount=Decimal("25.00")
|
||||
addon=mandatory_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("25.00")
|
||||
)
|
||||
|
||||
# Create mandatory unit rate addon
|
||||
|
@ -594,7 +601,7 @@ class VSHNAppCatAddonTestCase(TestCase):
|
|||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=optional_addon, currency=Currency.CHF, amount=Decimal("15.00")
|
||||
addon=optional_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("15.00")
|
||||
)
|
||||
|
||||
# Calculate price with selected addon
|
||||
|
|
|
@ -127,6 +127,7 @@ class PricingEdgeCasesTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("50.00"),
|
||||
)
|
||||
|
||||
|
@ -208,6 +209,7 @@ class PricingEdgeCasesTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=price_config,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("0.01"), # Very small base fee
|
||||
)
|
||||
|
||||
|
|
|
@ -204,7 +204,10 @@ class PricingIntegrationTestCase(TestCase):
|
|||
|
||||
for currency, amount in base_fees:
|
||||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price, currency=currency, amount=amount
|
||||
vshn_appcat_price_config=appcat_price, currency=currency, service_level=VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, amount=amount
|
||||
)
|
||||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price, currency=currency, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=amount
|
||||
)
|
||||
|
||||
# Set up unit rates for different service levels and currencies
|
||||
|
@ -237,7 +240,7 @@ class PricingIntegrationTestCase(TestCase):
|
|||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=backup_addon, currency=Currency.CHF, amount=Decimal("15.00")
|
||||
addon=backup_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("15.00")
|
||||
)
|
||||
|
||||
# Create optional addon (monitoring)
|
||||
|
@ -317,6 +320,7 @@ class PricingIntegrationTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.USD,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("30.00"),
|
||||
)
|
||||
|
||||
|
@ -390,6 +394,7 @@ class PricingIntegrationTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=redis_price,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("20.00"),
|
||||
)
|
||||
|
||||
|
@ -454,6 +459,7 @@ class PricingIntegrationTestCase(TestCase):
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("40.00"), # Base fee for managed service
|
||||
)
|
||||
|
||||
|
@ -474,7 +480,7 @@ class PricingIntegrationTestCase(TestCase):
|
|||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=backup_addon, currency=Currency.CHF, amount=Decimal("25.00")
|
||||
addon=backup_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("25.00")
|
||||
)
|
||||
|
||||
monitoring_addon = VSHNAppCatAddon.objects.create(
|
||||
|
@ -501,7 +507,541 @@ class PricingIntegrationTestCase(TestCase):
|
|||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=ssl_addon, currency=Currency.CHF, amount=Decimal("18.00")
|
||||
addon=ssl_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("18.00")
|
||||
)
|
||||
|
||||
# Calculate final price with all selected addons
|
||||
result = appcat_price.calculate_final_price(
|
||||
Currency.CHF,
|
||||
VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
16, # 16 GiB RAM
|
||||
addon_ids=[monitoring_addon.id, ssl_addon.id],
|
||||
)
|
||||
|
||||
# Expected calculation:
|
||||
# Base fee: 40.00
|
||||
# RAM cost: First 8 at 6.00 = 48.00, Next 8 at 5.40 (10% discount) = 43.20
|
||||
# RAM total: 91.20
|
||||
# Mandatory backup: 25.00
|
||||
# Optional monitoring: 0.75 * 16 = 12.00
|
||||
# Optional SSL: 18.00
|
||||
# Total: 40.00 + 91.20 + 25.00 + 12.00 + 18.00 = 186.20
|
||||
|
||||
self.assertEqual(result["total_price"], Decimal("186.20"))
|
||||
self.assertEqual(result["addon_total"], Decimal("55.00"))
|
||||
self.assertEqual(len(result["addon_breakdown"]), 3)
|
||||
|
||||
# Verify addon breakdown details
|
||||
addon_names = [addon["name"] for addon in result["addon_breakdown"]]
|
||||
self.assertIn("Enterprise Backup", addon_names)
|
||||
self.assertIn("Advanced Monitoring", addon_names)
|
||||
from decimal import Decimal
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from ..models.base import Currency, Term, Unit
|
||||
from ..models.providers import CloudProvider
|
||||
from ..models.services import Service, Category
|
||||
from ..models.pricing import (
|
||||
ComputePlan,
|
||||
ComputePlanPrice,
|
||||
ComputePlanGroup,
|
||||
StoragePlan,
|
||||
StoragePlanPrice,
|
||||
ProgressiveDiscountModel,
|
||||
DiscountTier,
|
||||
VSHNAppCatPrice,
|
||||
VSHNAppCatBaseFee,
|
||||
VSHNAppCatUnitRate,
|
||||
VSHNAppCatAddon,
|
||||
VSHNAppCatAddonBaseFee,
|
||||
VSHNAppCatAddonUnitRate,
|
||||
ExternalPricePlans,
|
||||
)
|
||||
|
||||
|
||||
class PricingIntegrationTestCase(TestCase):
|
||||
"""Integration tests for pricing models working together"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data for integration tests"""
|
||||
# Create cloud provider
|
||||
self.cloud_provider = CloudProvider.objects.create(
|
||||
name="VSHN Cloud",
|
||||
slug="vshn-cloud",
|
||||
description="Swiss cloud provider",
|
||||
website="https://vshn.ch",
|
||||
is_featured=True,
|
||||
)
|
||||
|
||||
# Create service category
|
||||
self.database_category = Category.objects.create(
|
||||
name="Databases", slug="databases", description="Database services"
|
||||
)
|
||||
|
||||
# Create database service
|
||||
self.postgresql_service = Service.objects.create(
|
||||
name="PostgreSQL",
|
||||
slug="postgresql",
|
||||
description="Managed PostgreSQL database service",
|
||||
tagline="Reliable, scalable PostgreSQL",
|
||||
features="High availability, automated backups, monitoring",
|
||||
is_featured=True,
|
||||
)
|
||||
self.postgresql_service.categories.add(self.database_category)
|
||||
|
||||
# Create compute plan group
|
||||
self.standard_group = ComputePlanGroup.objects.create(
|
||||
name="Standard",
|
||||
description="Standard compute plans",
|
||||
node_label="standard",
|
||||
order=1,
|
||||
)
|
||||
|
||||
# Create multiple compute plans
|
||||
self.small_plan = ComputePlan.objects.create(
|
||||
name="Small",
|
||||
vcpus=1.0,
|
||||
ram=2.0,
|
||||
cpu_mem_ratio=0.5,
|
||||
cloud_provider=self.cloud_provider,
|
||||
group=self.standard_group,
|
||||
term=Term.MTH,
|
||||
active=True,
|
||||
)
|
||||
|
||||
self.medium_plan = ComputePlan.objects.create(
|
||||
name="Medium",
|
||||
vcpus=2.0,
|
||||
ram=4.0,
|
||||
cpu_mem_ratio=0.5,
|
||||
cloud_provider=self.cloud_provider,
|
||||
group=self.standard_group,
|
||||
term=Term.MTH,
|
||||
active=True,
|
||||
)
|
||||
|
||||
self.large_plan = ComputePlan.objects.create(
|
||||
name="Large",
|
||||
vcpus=4.0,
|
||||
ram=8.0,
|
||||
cpu_mem_ratio=0.5,
|
||||
cloud_provider=self.cloud_provider,
|
||||
group=self.standard_group,
|
||||
term=Term.MTH,
|
||||
active=True,
|
||||
)
|
||||
|
||||
# Create storage plan
|
||||
self.ssd_storage = StoragePlan.objects.create(
|
||||
name="SSD Storage",
|
||||
cloud_provider=self.cloud_provider,
|
||||
term=Term.MTH,
|
||||
unit=Unit.GIB,
|
||||
)
|
||||
|
||||
# Create progressive discount model for AppCat
|
||||
self.ram_discount_model = ProgressiveDiscountModel.objects.create(
|
||||
name="RAM Volume Discount",
|
||||
description="Progressive discount for RAM usage",
|
||||
active=True,
|
||||
)
|
||||
|
||||
# Create discount tiers
|
||||
DiscountTier.objects.create(
|
||||
discount_model=self.ram_discount_model,
|
||||
min_units=0,
|
||||
max_units=8,
|
||||
discount_percent=Decimal("0.00"), # 0-7 GiB: no discount
|
||||
)
|
||||
|
||||
DiscountTier.objects.create(
|
||||
discount_model=self.ram_discount_model,
|
||||
min_units=8,
|
||||
max_units=32,
|
||||
discount_percent=Decimal("10.00"), # 8-31 GiB: 10% discount
|
||||
)
|
||||
|
||||
DiscountTier.objects.create(
|
||||
discount_model=self.ram_discount_model,
|
||||
min_units=32,
|
||||
max_units=None,
|
||||
discount_percent=Decimal("20.00"), # 32+ GiB: 20% discount
|
||||
)
|
||||
|
||||
def test_complete_pricing_setup(self):
|
||||
"""Test complete pricing setup for all models"""
|
||||
# Set up compute plan prices
|
||||
ComputePlanPrice.objects.create(
|
||||
compute_plan=self.small_plan, currency=Currency.CHF, amount=Decimal("50.00")
|
||||
)
|
||||
|
||||
ComputePlanPrice.objects.create(
|
||||
compute_plan=self.medium_plan,
|
||||
currency=Currency.CHF,
|
||||
amount=Decimal("100.00"),
|
||||
)
|
||||
|
||||
ComputePlanPrice.objects.create(
|
||||
compute_plan=self.large_plan,
|
||||
currency=Currency.CHF,
|
||||
amount=Decimal("200.00"),
|
||||
)
|
||||
|
||||
# Set up storage pricing
|
||||
StoragePlanPrice.objects.create(
|
||||
storage_plan=self.ssd_storage,
|
||||
currency=Currency.CHF,
|
||||
amount=Decimal("0.20"), # 0.20 CHF per GiB
|
||||
)
|
||||
|
||||
# Verify all prices are retrievable
|
||||
self.assertEqual(self.small_plan.get_price(Currency.CHF), Decimal("50.00"))
|
||||
self.assertEqual(self.medium_plan.get_price(Currency.CHF), Decimal("100.00"))
|
||||
self.assertEqual(self.large_plan.get_price(Currency.CHF), Decimal("200.00"))
|
||||
self.assertEqual(self.ssd_storage.get_price(Currency.CHF), Decimal("0.20"))
|
||||
|
||||
def test_multi_currency_pricing(self):
|
||||
"""Test pricing in multiple currencies"""
|
||||
# Set up prices in CHF, EUR, and USD
|
||||
currencies_and_rates = [
|
||||
(Currency.CHF, Decimal("100.00")),
|
||||
(Currency.EUR, Decimal("95.00")),
|
||||
(Currency.USD, Decimal("110.00")),
|
||||
]
|
||||
|
||||
for currency, amount in currencies_and_rates:
|
||||
ComputePlanPrice.objects.create(
|
||||
compute_plan=self.medium_plan, currency=currency, amount=amount
|
||||
)
|
||||
|
||||
# Verify all currencies are available
|
||||
for currency, expected_amount in currencies_and_rates:
|
||||
self.assertEqual(self.medium_plan.get_price(currency), expected_amount)
|
||||
|
||||
def test_appcat_service_with_complete_pricing(self):
|
||||
"""Test complete AppCat service pricing with all features"""
|
||||
# Create AppCat price configuration
|
||||
appcat_price = VSHNAppCatPrice.objects.create(
|
||||
service=self.postgresql_service,
|
||||
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
|
||||
term=Term.MTH,
|
||||
discount_model=self.ram_discount_model,
|
||||
ha_replica_min=1,
|
||||
ha_replica_max=3,
|
||||
public_display_enabled=True,
|
||||
)
|
||||
|
||||
# Set up base fees for different currencies
|
||||
base_fees = [
|
||||
(Currency.CHF, Decimal("25.00")),
|
||||
(Currency.EUR, Decimal("22.50")),
|
||||
(Currency.USD, Decimal("27.50")),
|
||||
]
|
||||
|
||||
for currency, amount in base_fees:
|
||||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price, currency=currency, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=amount
|
||||
)
|
||||
|
||||
# Set up unit rates for different service levels and currencies
|
||||
unit_rates = [
|
||||
(Currency.CHF, VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, Decimal("3.5000")),
|
||||
(Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, Decimal("5.0000")),
|
||||
(Currency.EUR, VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, Decimal("3.2000")),
|
||||
(Currency.EUR, VSHNAppCatPrice.ServiceLevel.GUARANTEED, Decimal("4.5000")),
|
||||
(Currency.USD, VSHNAppCatPrice.ServiceLevel.BEST_EFFORT, Decimal("3.8000")),
|
||||
(Currency.USD, VSHNAppCatPrice.ServiceLevel.GUARANTEED, Decimal("5.5000")),
|
||||
]
|
||||
|
||||
for currency, service_level, amount in unit_rates:
|
||||
VSHNAppCatUnitRate.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=currency,
|
||||
service_level=service_level,
|
||||
amount=amount,
|
||||
)
|
||||
|
||||
# Create mandatory addon (backup)
|
||||
backup_addon = VSHNAppCatAddon.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
name="Automated Backup",
|
||||
description="Daily automated backups with 30-day retention",
|
||||
commercial_description="Never lose your data with automated daily backups",
|
||||
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
|
||||
mandatory=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=backup_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("15.00")
|
||||
)
|
||||
|
||||
# Create optional addon (monitoring)
|
||||
monitoring_addon = VSHNAppCatAddon.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
name="Advanced Monitoring",
|
||||
description="Detailed monitoring with custom alerts",
|
||||
commercial_description="Get insights into your database performance",
|
||||
addon_type=VSHNAppCatAddon.AddonType.UNIT_RATE,
|
||||
mandatory=False,
|
||||
order=2,
|
||||
)
|
||||
|
||||
VSHNAppCatAddonUnitRate.objects.create(
|
||||
addon=monitoring_addon,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("0.5000"),
|
||||
)
|
||||
|
||||
# Test price calculation scenarios
|
||||
|
||||
# Scenario 1: Small setup (4 GiB RAM, no discount)
|
||||
result_small = appcat_price.calculate_final_price(
|
||||
Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 4
|
||||
)
|
||||
|
||||
# Base: 25 + (5 * 4) = 45
|
||||
# Mandatory backup: 15
|
||||
# Total: 60
|
||||
self.assertEqual(result_small["total_price"], Decimal("60.00"))
|
||||
self.assertEqual(result_small["addon_total"], Decimal("15.00"))
|
||||
self.assertEqual(len(result_small["addon_breakdown"]), 1)
|
||||
|
||||
# Scenario 2: Medium setup (16 GiB RAM, partial discount)
|
||||
result_medium = appcat_price.calculate_final_price(
|
||||
Currency.CHF, VSHNAppCatPrice.ServiceLevel.GUARANTEED, 16
|
||||
)
|
||||
|
||||
# First 8 GiB at full rate: 5 * 8 = 40
|
||||
# Next 8 GiB at 90% (10% discount): 5 * 0.9 * 8 = 36
|
||||
# Unit cost: 76
|
||||
# Base: 25 + 76 = 101
|
||||
# Mandatory backup: 15
|
||||
# Total: 116
|
||||
self.assertEqual(result_medium["total_price"], Decimal("116.00"))
|
||||
|
||||
# Scenario 3: Large setup with optional addon (40 GiB RAM, full discount tiers)
|
||||
result_large = appcat_price.calculate_final_price(
|
||||
Currency.CHF,
|
||||
VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
40,
|
||||
addon_ids=[monitoring_addon.id],
|
||||
)
|
||||
|
||||
# First 8 GiB at full rate: 5 * 8 = 40
|
||||
# Next 24 GiB at 90% (10% discount): 5 * 0.9 * 24 = 108
|
||||
# Next 8 GiB at 80% (20% discount): 5 * 0.8 * 8 = 32
|
||||
# Unit cost: 180
|
||||
# Base: 25 + 180 = 205
|
||||
# Mandatory backup: 15
|
||||
# Optional monitoring: 0.5 * 40 = 20
|
||||
# Total: 240
|
||||
self.assertEqual(result_large["total_price"], Decimal("240.00"))
|
||||
self.assertEqual(result_large["addon_total"], Decimal("35.00"))
|
||||
self.assertEqual(len(result_large["addon_breakdown"]), 2)
|
||||
|
||||
def test_external_price_comparison_integration(self):
|
||||
"""Test external price comparison with internal pricing"""
|
||||
# Set up internal pricing
|
||||
appcat_price = VSHNAppCatPrice.objects.create(
|
||||
service=self.postgresql_service,
|
||||
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
|
||||
term=Term.MTH,
|
||||
)
|
||||
|
||||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.USD,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("30.00"),
|
||||
)
|
||||
|
||||
VSHNAppCatUnitRate.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.USD,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("4.0000"),
|
||||
)
|
||||
|
||||
# Create external competitor pricing
|
||||
aws_provider = CloudProvider.objects.create(
|
||||
name="AWS",
|
||||
slug="aws",
|
||||
description="Amazon Web Services",
|
||||
website="https://aws.amazon.com",
|
||||
)
|
||||
|
||||
external_price = ExternalPricePlans.objects.create(
|
||||
plan_name="RDS PostgreSQL db.t3.medium",
|
||||
description="AWS RDS PostgreSQL instance",
|
||||
source="https://aws.amazon.com/rds/postgresql/pricing/",
|
||||
cloud_provider=aws_provider,
|
||||
service=self.postgresql_service,
|
||||
vshn_appcat_price=appcat_price,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
currency=Currency.USD,
|
||||
term=Term.MTH,
|
||||
amount=Decimal("62.56"), # Monthly cost for db.t3.medium
|
||||
vcpus=2.0,
|
||||
ram=4.0, # 4 GiB RAM
|
||||
storage=20.0, # 20 GiB storage included
|
||||
competitor_sla="99.95%",
|
||||
replicas=1,
|
||||
)
|
||||
|
||||
# Compare internal vs external pricing for equivalent setup
|
||||
internal_result = appcat_price.calculate_final_price(
|
||||
Currency.USD,
|
||||
VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
4, # 4 GiB RAM to match external offering
|
||||
)
|
||||
|
||||
# Internal: 30 + (4 * 4) = 46 USD
|
||||
internal_price = internal_result["total_price"]
|
||||
external_price_amount = external_price.amount
|
||||
|
||||
self.assertEqual(internal_price, Decimal("46.00"))
|
||||
self.assertEqual(external_price_amount, Decimal("62.56"))
|
||||
|
||||
# Verify our pricing is competitive
|
||||
self.assertLess(internal_price, external_price_amount)
|
||||
|
||||
def test_service_availability_with_pricing(self):
|
||||
"""Test service availability based on pricing configuration"""
|
||||
# Create service with pricing but not enabled for public display
|
||||
redis_service = Service.objects.create(
|
||||
name="Redis",
|
||||
slug="redis",
|
||||
description="In-memory data store",
|
||||
features="High performance caching",
|
||||
)
|
||||
|
||||
redis_price = VSHNAppCatPrice.objects.create(
|
||||
service=redis_service,
|
||||
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
|
||||
term=Term.MTH,
|
||||
public_display_enabled=False, # Private pricing
|
||||
)
|
||||
|
||||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=redis_price,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("20.00"),
|
||||
)
|
||||
|
||||
# Service should exist but not be publicly available for pricing
|
||||
self.assertFalse(redis_price.public_display_enabled)
|
||||
|
||||
# Enable public display
|
||||
redis_price.public_display_enabled = True
|
||||
redis_price.save()
|
||||
|
||||
self.assertTrue(redis_price.public_display_enabled)
|
||||
|
||||
def test_pricing_model_relationships(self):
|
||||
"""Test all pricing model relationships work correctly"""
|
||||
# Verify cloud provider relationships
|
||||
self.assertEqual(self.cloud_provider.compute_plans.count(), 3)
|
||||
self.assertEqual(self.cloud_provider.storage_plans.count(), 1)
|
||||
|
||||
# Verify service relationships
|
||||
self.assertTrue(hasattr(self.postgresql_service, "vshn_appcat_price"))
|
||||
|
||||
# Verify compute plan group relationships
|
||||
self.assertEqual(self.standard_group.compute_plans.count(), 3)
|
||||
|
||||
# Create and verify discount model relationships
|
||||
appcat_price = VSHNAppCatPrice.objects.create(
|
||||
service=self.postgresql_service,
|
||||
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
|
||||
term=Term.MTH,
|
||||
discount_model=self.ram_discount_model,
|
||||
)
|
||||
|
||||
self.assertEqual(self.ram_discount_model.price_configs.count(), 1)
|
||||
self.assertEqual(self.ram_discount_model.tiers.count(), 3)
|
||||
|
||||
# Test cascade deletions work properly
|
||||
service_id = self.postgresql_service.id
|
||||
appcat_price_id = appcat_price.id
|
||||
|
||||
# Delete service should cascade to appcat price
|
||||
self.postgresql_service.delete()
|
||||
|
||||
with self.assertRaises(VSHNAppCatPrice.DoesNotExist):
|
||||
VSHNAppCatPrice.objects.get(id=appcat_price_id)
|
||||
|
||||
def test_comprehensive_pricing_scenario(self):
|
||||
"""Test a comprehensive real-world pricing scenario"""
|
||||
# Company needs PostgreSQL with high availability
|
||||
# Requirements: 16 GiB RAM, automated backups, monitoring, SSL
|
||||
|
||||
appcat_price = VSHNAppCatPrice.objects.create(
|
||||
service=self.postgresql_service,
|
||||
variable_unit=VSHNAppCatPrice.VariableUnit.RAM,
|
||||
term=Term.MTH,
|
||||
discount_model=self.ram_discount_model,
|
||||
ha_replica_min=2,
|
||||
ha_replica_max=3,
|
||||
public_display_enabled=True,
|
||||
)
|
||||
|
||||
# Set up pricing
|
||||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("40.00"), # Base fee for managed service
|
||||
)
|
||||
|
||||
VSHNAppCatUnitRate.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("6.0000"), # CHF per GiB RAM
|
||||
)
|
||||
|
||||
# Create all required addons
|
||||
backup_addon = VSHNAppCatAddon.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
name="Enterprise Backup",
|
||||
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
|
||||
mandatory=True,
|
||||
order=1,
|
||||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=backup_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("25.00")
|
||||
)
|
||||
|
||||
monitoring_addon = VSHNAppCatAddon.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
name="Advanced Monitoring",
|
||||
addon_type=VSHNAppCatAddon.AddonType.UNIT_RATE,
|
||||
mandatory=False,
|
||||
order=2,
|
||||
)
|
||||
|
||||
VSHNAppCatAddonUnitRate.objects.create(
|
||||
addon=monitoring_addon,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=Decimal("0.7500"),
|
||||
)
|
||||
|
||||
ssl_addon = VSHNAppCatAddon.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
name="SSL Certificate",
|
||||
addon_type=VSHNAppCatAddon.AddonType.BASE_FEE,
|
||||
mandatory=False,
|
||||
order=3,
|
||||
)
|
||||
|
||||
VSHNAppCatAddonBaseFee.objects.create(
|
||||
addon=ssl_addon, currency=Currency.CHF, service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED, amount=Decimal("18.00")
|
||||
)
|
||||
|
||||
# Calculate final price with all selected addons
|
||||
|
|
|
@ -81,6 +81,7 @@ class PricingTestMixin:
|
|||
VSHNAppCatBaseFee.objects.create(
|
||||
vshn_appcat_price_config=appcat_price,
|
||||
currency=Currency.CHF,
|
||||
service_level=VSHNAppCatPrice.ServiceLevel.GUARANTEED,
|
||||
amount=base_fee,
|
||||
)
|
||||
|
||||
|
|
|
@ -340,7 +340,7 @@ def generate_pricing_data(offering):
|
|||
|
||||
# Get pricing components
|
||||
compute_plan_price = plan.get_price(currency)
|
||||
base_fee = appcat_price.get_base_fee(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
|
||||
|
@ -380,7 +380,7 @@ def generate_pricing_data(offering):
|
|||
addon_price_per_unit = None
|
||||
|
||||
if addon.addon_type == "BF": # Base Fee
|
||||
addon_price = addon.get_price(currency)
|
||||
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:
|
||||
|
|
|
@ -60,7 +60,7 @@ def get_internal_cloud_provider_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_base_fee = appcat_price.get_base_fee(currency, service_level)
|
||||
compare_unit_rate = appcat_price.get_unit_rate(currency, service_level)
|
||||
|
||||
# Skip if any pricing component is missing
|
||||
|
@ -239,7 +239,7 @@ def pricelist(request):
|
|||
|
||||
# Get pricing components
|
||||
compute_plan_price = plan.get_price(currency)
|
||||
base_fee = appcat_price.get_base_fee(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
|
||||
|
@ -340,7 +340,7 @@ def pricelist(request):
|
|||
addon_price = None
|
||||
|
||||
if addon.addon_type == "BF": # Base Fee
|
||||
addon_price = addon.get_price(currency)
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue