mirror of
https://github.com/ae-utbm/sith.git
synced 2026-06-13 11:39:25 +00:00
enforce max amount on sith account
This commit is contained in:
+49
-4
@@ -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
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user