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 decimal import Decimal
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core import checks
|
||||||
|
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
|
||||||
class CurrencyField(models.DecimalField):
|
class CurrencyField(models.DecimalField):
|
||||||
"""Custom database field used for currency."""
|
"""Custom database field used for currency."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(
|
||||||
kwargs["max_digits"] = 12
|
self, verbose_name=None, name=None, min_value=None, max_value=None, **kwargs
|
||||||
kwargs["decimal_places"] = 2
|
):
|
||||||
super().__init__(*args, **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):
|
def to_python(self, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
return super().to_python(value).quantize(Decimal("0.01"))
|
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:
|
if settings.TESTING:
|
||||||
from model_bakery import baker
|
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.conf import settings
|
||||||
from django.core.validators import MinLengthValidator
|
from django.core.validators import MinLengthValidator
|
||||||
from django.db import models
|
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.db.models.functions import Coalesce, Concat, Length
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -99,7 +99,9 @@ class Customer(models.Model):
|
|||||||
|
|
||||||
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
|
||||||
account_id = models.CharField(_("account id"), max_length=10, unique=True)
|
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()
|
objects = CustomerQuerySet.as_manager()
|
||||||
|
|
||||||
@@ -156,13 +158,15 @@ class Customer(models.Model):
|
|||||||
unique_fields=["customer", "returnable"],
|
unique_fields=["customer", "returnable"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def can_buy(self) -> bool:
|
def can_buy(self) -> bool:
|
||||||
"""Check if whether this customer has the right to purchase any item."""
|
"""Check if whether this customer has the right to purchase any item."""
|
||||||
subscription = self.user.subscriptions.order_by("subscription_end").last()
|
subscription_end = self.user.subscriptions.aggregate(
|
||||||
if subscription is None:
|
res=Max("subscription_end")
|
||||||
|
).get("res")
|
||||||
|
if subscription_end is None:
|
||||||
return False
|
return False
|
||||||
return (date.today() - subscription.subscription_end) < timedelta(days=90)
|
return (date.today() - subscription_end) < timedelta(days=90)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_or_create(cls, user: User) -> tuple[Customer, bool]:
|
def get_or_create(cls, user: User) -> tuple[Customer, bool]:
|
||||||
@@ -823,7 +827,7 @@ class Refilling(models.Model):
|
|||||||
counter = models.ForeignKey(
|
counter = models.ForeignKey(
|
||||||
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
|
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
amount = CurrencyField(_("amount"))
|
amount = CurrencyField(_("amount"), min_value=0.01)
|
||||||
operator = models.ForeignKey(
|
operator = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
related_name="refillings_as_operator",
|
related_name="refillings_as_operator",
|
||||||
|
|||||||
@@ -503,6 +503,8 @@ SITH_ACCOUNT_INACTIVITY_DELTA = relativedelta(years=2)
|
|||||||
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
|
SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
|
||||||
"""timedelta between the warning mail and the actual account dump"""
|
"""timedelta between the warning mail and the actual account dump"""
|
||||||
|
|
||||||
|
SITH_ACCOUNT_MAX_MONEY = 250 # €
|
||||||
|
|
||||||
# Defines which product type is the refilling type,
|
# Defines which product type is the refilling type,
|
||||||
# and thus increases the account amount
|
# and thus increases the account amount
|
||||||
SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int(
|
SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int(
|
||||||
|
|||||||
Reference in New Issue
Block a user