diff --git a/accounting/admin.py b/accounting/admin.py index 2bb63841..72489b4a 100644 --- a/accounting/admin.py +++ b/accounting/admin.py @@ -28,7 +28,12 @@ class RegistrationSectionAdmin(ModelAdminImproved): @admin.register(ChildProduct) class ChildProductAdmin(ModelAdminImproved): - fields = ("child_product", "quantity") + fields = ("child_product", "description", "quantity") + + +@admin.register(SpecificProduct) +class SpecificProductAdmin(ModelAdminImproved): + fields = ("specific_product", "description", "unit_price") class YearDescendingFilter(admin.SimpleListFilter): diff --git a/accounting/api.py b/accounting/api.py index e111e706..eb2a1cb3 100644 --- a/accounting/api.py +++ b/accounting/api.py @@ -6,6 +6,7 @@ Order, Product, RegistrationSection, + SpecificProduct, ) @@ -70,14 +71,25 @@ class Meta: child_product = ProductChildSerializer() +class SpecificProductSerializer(serializers.ModelSerializer): + class Meta: + model = SpecificProduct + read_only_fields = ("unit_price", "specific_product") + fields = read_only_fields + + specific_product = ProductChildSerializer() + + class ProductSerializer(ProductChildSerializer): class Meta(ProductChildSerializer.Meta): read_only_fields = ProductChildSerializer.Meta.read_only_fields + ( "child_products", + "specific_products", ) fields = ProductChildSerializer.Meta.fields + read_only_fields child_products = ChildProductSerializer(many=True) + specific_products = SpecificProductSerializer(many=True) class OrderSerializer(serializers.ModelSerializer): diff --git a/accounting/migrations/0035_add_specific_products.py b/accounting/migrations/0035_add_specific_products.py new file mode 100644 index 00000000..3e71e3ff --- /dev/null +++ b/accounting/migrations/0035_add_specific_products.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2.24 on 2024-08-04 20:50 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0034_change_exclusive_for_choices'), + ] + + operations = [ + migrations.CreateModel( + name='SpecificProduct', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('unit_price', models.IntegerField()), + ('specific_product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounting.Product')), + ], + ), + migrations.AddField( + model_name='product', + name='specific_products', + field=models.ManyToManyField(blank=True, help_text='(ONLY RELEVANT FOR PACKAGES AS ROOT PRODUCTS) These products will only be shown to the customer if they select the current package This feature was used in 2024 when silver and bronze packages lead to different prices on the same products', to='accounting.SpecificProduct'), + ), + ] diff --git a/accounting/migrations/0036_add_specific_product_description.py b/accounting/migrations/0036_add_specific_product_description.py new file mode 100644 index 00000000..df17a3cf --- /dev/null +++ b/accounting/migrations/0036_add_specific_product_description.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.24 on 2024-08-04 20:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0035_add_specific_products'), + ] + + operations = [ + migrations.AddField( + model_name='specificproduct', + name='description', + field=models.TextField(blank=True, null=True), + ), + ] diff --git a/accounting/migrations/0037_add_product_help_texts.py b/accounting/migrations/0037_add_product_help_texts.py new file mode 100644 index 00000000..929c9c72 --- /dev/null +++ b/accounting/migrations/0037_add_product_help_texts.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.24 on 2024-08-04 20:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0036_add_specific_product_description'), + ] + + operations = [ + migrations.AlterField( + model_name='childproduct', + name='description', + field=models.TextField(blank=True, help_text='Optional, describe why you created this child product. This will not be shown to the customer.', null=True), + ), + migrations.AlterField( + model_name='specificproduct', + name='description', + field=models.TextField(blank=True, help_text='Optional, describe why you created this specific product. This will not be shown to the customer.', null=True), + ), + ] diff --git a/accounting/migrations/0038_add_display_only_when_specific_for_packages.py b/accounting/migrations/0038_add_display_only_when_specific_for_packages.py new file mode 100644 index 00000000..db09aa7d --- /dev/null +++ b/accounting/migrations/0038_add_display_only_when_specific_for_packages.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.24 on 2024-08-04 21:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0037_add_product_help_texts'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='display_only_when_specific_for_packages', + field=models.BooleanField(default=False, help_text="This product will only be shown to the customer if they select a package that has this product as a specific product. When to use this: e.g. A package is gatekeeping this product, only show the product when the package is selected. When not to use this: e.g. This product's price is only affected by one or more packages, but the product is still available for purchase without the package."), + ), + migrations.AlterField( + model_name='product', + name='specific_products', + field=models.ManyToManyField(blank=True, help_text='(ONLY RELEVANT FOR PACKAGES AS ROOT PRODUCTS) These products will only be shown to the customer if they select the current package This feature was used in 2024 when silver and bronze packages lead to different prices on the same products Neccessary is to toggle the "Display in product list" to false on for the specific product (otherwise it will be displayed with standard price).', to='accounting.SpecificProduct'), + ), + ] diff --git a/accounting/models.py b/accounting/models.py index 423c88bc..ed978717 100644 --- a/accounting/models.py +++ b/accounting/models.py @@ -53,9 +53,28 @@ def __str__(self): return self.name +class SpecificProduct(models.Model): + unit_price = models.IntegerField(blank=False) + description = models.TextField( + blank=True, + null=True, + help_text="Optional, describe why you created this specific product. This will not be shown to the customer.", + ) + specific_product = models.ForeignKey( + "Product", blank=False, on_delete=models.CASCADE + ) + + def __str__(self): + return "%s á %s SEK" % (self.specific_product, self.unit_price) + + class ChildProduct(models.Model): quantity = models.PositiveIntegerField(blank=False) - description = models.TextField(blank=True, null=True) + description = models.TextField( + blank=True, + null=True, + help_text="Optional, describe why you created this child product. This will not be shown to the customer.", + ) child_product = models.ForeignKey("Product", blank=False, on_delete=models.CASCADE) def __str__(self): @@ -156,6 +175,19 @@ class Product(models.Model): ), ) + specific_products = models.ManyToManyField( + SpecificProduct, + blank=True, + help_text=" ".join( + [ + "(ONLY RELEVANT FOR PACKAGES AS ROOT PRODUCTS)", + "These products will only be shown to the customer if they select the current package", + "This feature was used in 2024 when silver and bronze packages lead to different prices on the same products", + 'Neccessary is to toggle the "Display in product list" to false on for the specific product (otherwise it will be displayed with standard price).', + ] + ), + ) + display_in_product_list = models.BooleanField( default=True, help_text=" ".join( @@ -168,6 +200,17 @@ class Product(models.Model): ), ) + display_only_when_specific_for_packages = models.BooleanField( + default=False, + help_text=" ".join( + [ + "This product will only be shown to the customer if they select a package that has this product as a specific product.", + "When to use this: e.g. A package is gatekeeping this product, only show the product when the package is selected.", + "When not to use this: e.g. This product's price is only affected by one or more packages, but the product is still available for purchase without the package.", + ] + ), + ) + class Meta: verbose_name_plural = "Products" ordering = ["ordering"] diff --git a/templates/register/email/cr_complete.html b/templates/register/email/cr_complete.html index 47aa3d5b..4a02e179 100644 --- a/templates/register/email/cr_complete.html +++ b/templates/register/email/cr_complete.html @@ -15,8 +15,12 @@ {% include 'email/button.html' with content="Go to dashboard" url="https://ais.armada.nu/register" %} {% include 'email/divider.html' %}
- Please note that this is an automatically generated email. If you have any questions, please contact your sales - contact person. + Please note that this is an automatically generated email. + If you have any questions about your registration, contact sales@armada.nu. + Otherwise, you can wait until the host assigned to you contacts you with more information closer to the fair. +
++ If you haven’t filled in your company information in the dashboard yet, please do so.
We look forward to seeing you at the fair! diff --git a/util/product.py b/util/product.py index 0d41efc5..40d46ba5 100644 --- a/util/product.py +++ b/util/product.py @@ -18,4 +18,7 @@ def get_products(fair, company): else: q |= Q(exclusively_for__contains=["ir-late"]) - return Product.objects.filter(revenue__fair=fair).filter(q) + return Product.objects.filter( + revenue__fair=fair, + display_only_when_specific_for_packages=False, + ).filter(q)