From 3b8eea9c14ae9051d5e729dd99c3b99dde78159b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 23 Jun 2025 10:00:19 +0200 Subject: [PATCH] model for classic plan pricing --- .../0037_remove_plan_pricing_planprice.py | 63 +++++++++++++++++++ hub/services/models/services.py | 37 ++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 hub/services/migrations/0037_remove_plan_pricing_planprice.py diff --git a/hub/services/migrations/0037_remove_plan_pricing_planprice.py b/hub/services/migrations/0037_remove_plan_pricing_planprice.py new file mode 100644 index 0000000..5326161 --- /dev/null +++ b/hub/services/migrations/0037_remove_plan_pricing_planprice.py @@ -0,0 +1,63 @@ +# Generated by Django 5.2 on 2025-06-23 07:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("services", "0036_alter_vshnappcataddonbasefee_options_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="plan", + name="pricing", + ), + migrations.CreateModel( + name="PlanPrice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "currency", + models.CharField( + choices=[ + ("CHF", "Swiss Franc"), + ("EUR", "Euro"), + ("USD", "US Dollar"), + ], + max_length=3, + ), + ), + ( + "amount", + models.DecimalField( + decimal_places=2, + help_text="Price in the specified currency, excl. VAT", + max_digits=10, + ), + ), + ( + "plan", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="plan_prices", + to="services.plan", + ), + ), + ], + options={ + "ordering": ["currency"], + "unique_together": {("plan", "currency")}, + }, + ), + ] diff --git a/hub/services/models/services.py b/hub/services/models/services.py index 5c155b8..e5b90cb 100644 --- a/hub/services/models/services.py +++ b/hub/services/models/services.py @@ -5,7 +5,13 @@ from django.urls import reverse from django.utils.text import slugify from django_prose_editor.fields import ProseEditorField -from .base import Category, ReusableText, ManagedServiceProvider, validate_image_size +from .base import ( + Category, + ReusableText, + ManagedServiceProvider, + validate_image_size, + Currency, +) from .providers import CloudProvider @@ -97,10 +103,31 @@ class ServiceOffering(models.Model): ) +class PlanPrice(models.Model): + plan = models.ForeignKey( + "Plan", on_delete=models.CASCADE, related_name="plan_prices" + ) + currency = models.CharField( + max_length=3, + choices=Currency.choices, + ) + amount = models.DecimalField( + max_digits=10, + decimal_places=2, + help_text="Price in the specified currency, excl. VAT", + ) + + class Meta: + unique_together = ("plan", "currency") + ordering = ["currency"] + + def __str__(self): + return f"{self.plan.name} - {self.amount} {self.currency}" + + class Plan(models.Model): name = models.CharField(max_length=100) description = ProseEditorField(blank=True, null=True) - pricing = ProseEditorField(blank=True, null=True) plan_description = models.ForeignKey( ReusableText, on_delete=models.PROTECT, @@ -122,6 +149,12 @@ class Plan(models.Model): def __str__(self): return f"{self.offering} - {self.name}" + def get_price(self, currency_code: str): + price_obj = PlanPrice.objects.filter(plan=self, currency=currency_code).first() + if price_obj: + return price_obj.amount + return None + class ExternalLinkOffering(models.Model): offering = models.ForeignKey(