enforce max amount on sith account

This commit is contained in:
imperosol
2026-06-07 14:16:12 +02:00
parent 1957aa1fdb
commit f6f31af975
4 changed files with 92 additions and 11 deletions
+49 -4
View File
@@ -1,22 +1,67 @@
from decimal import Decimal
from django.conf import settings
from django.core import checks
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.utils.functional import cached_property
class CurrencyField(models.DecimalField):
"""Custom database field used for currency."""
def __init__(self, *args, **kwargs):
kwargs["max_digits"] = 12
kwargs["decimal_places"] = 2
super().__init__(*args, **kwargs)
def __init__(
self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs
):
kwargs.update({"max_digits": 12, "decimal_places": 2})
self.min_value = min_value
self.max_value = max_value
super().__init__(verbose_name, name, **kwargs)
def to_python(self, value):
if value is None:
return None
return super().to_python(value).quantize(Decimal("0.01"))
@cached_property
def validators(self):
res = []
if self.max_value:
res.append(MaxValueValidator(self.max_value))
if self.min_value:
res.append(MinValueValidator(self.min_value))
return [*super().validators, *res]
def check(self, **kwargs):
errors = super().check(**kwargs)
for name, val in ("min_value", self.min_value), ("max_value", self.max_value):
if not val:
continue
try:
float(val)
except ValueError:
errors.append(
checks.Error(
f"CurrencyField.{name} must be a valid float",
obj=self,
id="sith.E001",
)
)
return errors
def formfield(self, **kwargs):
return super().formfield(
**{"min_value": self.min_value, "max_value": self.max_value, **kwargs}
)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self.min_value is not None:
kwargs["min_value"] = self.min_value
if self.max_value is not None:
kwargs["max_value"] = self.max_value
return name, path, args, kwargs
if settings.TESTING:
from model_bakery import baker
@@ -0,0 +1,30 @@
# Generated by Django 5.2.15 on 2026-06-07 12:08
from django.db import migrations
import counter.fields
class Migration(migrations.Migration):
dependencies = [("counter", "0041_alter_billinginfo_country_and_more")]
operations = [
migrations.AlterField(
model_name="customer",
name="amount",
field=counter.fields.CurrencyField(
decimal_places=2,
default=0,
max_digits=12,
max_value=250,
verbose_name="amount",
),
),
migrations.AlterField(
model_name="refilling",
name="amount",
field=counter.fields.CurrencyField(
decimal_places=2, max_digits=12, min_value=0.01, verbose_name="amount"
),
),
]
+11 -7
View File
@@ -28,7 +28,7 @@ from dict2xml import dict2xml
from django.conf import settings
from django.core.validators import MinLengthValidator
from django.db import models
from django.db.models import Exists, F, OuterRef, Q, QuerySet, Subquery, Sum, Value
from django.db.models import Exists, F, Max, OuterRef, Q, QuerySet, Subquery, Sum, Value
from django.db.models.functions import Coalesce, Concat, Length
from django.forms import ValidationError
from django.urls import reverse
@@ -99,7 +99,9 @@ class Customer(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
account_id = models.CharField(_("account id"), max_length=10, unique=True)
amount = CurrencyField(_("amount"), default=0)
amount = CurrencyField(
_("amount"), max_value=settings.SITH_ACCOUNT_MAX_MONEY, default=0
)
objects = CustomerQuerySet.as_manager()
@@ -156,13 +158,15 @@ class Customer(models.Model):
unique_fields=["customer", "returnable"],
)
@property
@cached_property
def can_buy(self) -> bool:
"""Check if whether this customer has the right to purchase any item."""
subscription = self.user.subscriptions.order_by("subscription_end").last()
if subscription is None:
subscription_end = self.user.subscriptions.aggregate(
res=Max("subscription_end")
).get("res")
if subscription_end is None:
return False
return (date.today() - subscription.subscription_end) < timedelta(days=90)
return (date.today() - subscription_end) < timedelta(days=90)
@classmethod
def get_or_create(cls, user: User) -> tuple[Customer, bool]:
@@ -823,7 +827,7 @@ class Refilling(models.Model):
counter = models.ForeignKey(
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
)
amount = CurrencyField(_("amount"))
amount = CurrencyField(_("amount"), min_value=0.01)
operator = models.ForeignKey(
User,
related_name="refillings_as_operator",
+2
View File
@@ -503,6 +503,8 @@ SITH_ACCOUNT_INACTIVITY_DELTA = relativedelta(years=2)
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
"""timedelta between the warning mail and the actual account dump"""
SITH_ACCOUNT_MAX_MONEY = 250 # €
# Defines which product type is the refilling type,
# and thus increases the account amount
SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int(