mirror of
https://github.com/ae-utbm/sith.git
synced 2026-06-13 03:29:19 +00:00
max amount for eboutic refills
This commit is contained in:
+2
-1
@@ -32,7 +32,8 @@ class CurrencyField(models.DecimalField):
|
||||
res.append(MinValueValidator(self.min_value))
|
||||
return [*super().validators, *res]
|
||||
|
||||
def check(self, **kwargs):
|
||||
def check(self, **kwargs): # pragma: no cover
|
||||
# this is executed during runserver, but won't run in prod
|
||||
errors = super().check(**kwargs)
|
||||
for name, val in ("min_value", self.min_value), ("max_value", self.max_value):
|
||||
if not val:
|
||||
|
||||
+20
-16
@@ -565,16 +565,7 @@ class BasketItemForm(forms.Form):
|
||||
quantity = forms.IntegerField(min_value=1, required=True)
|
||||
price_id = forms.IntegerField(min_value=0, required=True)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
customer: Customer,
|
||||
counter: Counter,
|
||||
allowed_prices: dict[int, Price],
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
self.customer = customer # Used by formset
|
||||
self.counter = counter # Used by formset
|
||||
def __init__(self, allowed_prices: dict[int, Price], *args, **kwargs):
|
||||
self.allowed_prices = allowed_prices
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -609,6 +600,11 @@ class BasketItemForm(forms.Form):
|
||||
|
||||
|
||||
class BaseBasketForm(forms.BaseFormSet):
|
||||
def __init__(self, *args, customer: Customer, counter: Counter, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.customer = customer
|
||||
self.counter = counter
|
||||
|
||||
def clean(self):
|
||||
self.forms = [form for form in self.forms if form.cleaned_data != {}]
|
||||
|
||||
@@ -617,8 +613,9 @@ class BaseBasketForm(forms.BaseFormSet):
|
||||
|
||||
self._check_forms_have_errors()
|
||||
self._check_product_are_unique()
|
||||
self._check_recorded_products(self[0].customer)
|
||||
self._check_enough_money(self[0].counter, self[0].customer)
|
||||
self._check_recorded_products()
|
||||
self._check_enough_money()
|
||||
self._check_refills()
|
||||
|
||||
def _check_forms_have_errors(self):
|
||||
if any(len(form.errors) > 0 for form in self):
|
||||
@@ -629,12 +626,12 @@ class BaseBasketForm(forms.BaseFormSet):
|
||||
if len(price_ids) != len(self.forms):
|
||||
raise forms.ValidationError(_("Duplicated product entries."))
|
||||
|
||||
def _check_enough_money(self, counter: Counter, customer: Customer):
|
||||
def _check_enough_money(self):
|
||||
self.total_price = sum([data["total_price"] for data in self.cleaned_data])
|
||||
if self.total_price > customer.amount:
|
||||
if self.total_price > self.customer.amount:
|
||||
raise forms.ValidationError(_("Not enough money"))
|
||||
|
||||
def _check_recorded_products(self, customer: Customer):
|
||||
def _check_recorded_products(self):
|
||||
"""Check for, among other things, ecocups and pitchers"""
|
||||
items = defaultdict(int)
|
||||
for form in self.forms:
|
||||
@@ -643,7 +640,7 @@ class BaseBasketForm(forms.BaseFormSet):
|
||||
returnables = list(
|
||||
ReturnableProduct.objects.filter(
|
||||
Q(product_id__in=ids) | Q(returned_product_id__in=ids)
|
||||
).annotate_balance_for(customer)
|
||||
).annotate_balance_for(self.customer)
|
||||
)
|
||||
limit_reached = []
|
||||
for returnable in returnables:
|
||||
@@ -662,6 +659,13 @@ class BaseBasketForm(forms.BaseFormSet):
|
||||
% ", ".join([str(p) for p in limit_reached])
|
||||
)
|
||||
|
||||
def _check_refills(self):
|
||||
refill_type_id = settings.SITH_COUNTER_PRODUCTTYPE_REFILLING
|
||||
if any(f.price.product.product_type_id == refill_type_id for f in self.forms):
|
||||
raise ValidationError(
|
||||
_("Refill bonds cannot be purchased outside of the eboutic")
|
||||
)
|
||||
|
||||
|
||||
BasketForm = forms.formset_factory(
|
||||
BasketItemForm, formset=BaseBasketForm, absolute_max=None, min_num=1
|
||||
|
||||
+2
-2
@@ -827,7 +827,7 @@ class Refilling(models.Model):
|
||||
counter = models.ForeignKey(
|
||||
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
|
||||
)
|
||||
amount = CurrencyField(_("amount"), min_value=0.01)
|
||||
amount: CurrencyField = CurrencyField(_("amount"), min_value=0.01)
|
||||
operator = models.ForeignKey(
|
||||
User,
|
||||
related_name="refillings_as_operator",
|
||||
@@ -883,7 +883,7 @@ class Refilling(models.Model):
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
if self.amount + self.customer.amount > settings.SITH_ACCOUNT_MAX_MONEY:
|
||||
if (self.amount + self.customer.amount) > settings.SITH_ACCOUNT_MAX_MONEY:
|
||||
raise ValidationError(
|
||||
_("There cannot be more than %(money)d€ on an AE account")
|
||||
% {"money": settings.SITH_ACCOUNT_MAX_MONEY}
|
||||
|
||||
@@ -73,13 +73,13 @@ class CounterClick(
|
||||
current_tab = "counter"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["form_kwargs"] = {
|
||||
return super().get_form_kwargs() | {
|
||||
"customer": self.customer,
|
||||
"counter": self.object,
|
||||
"allowed_prices": {price.id: price for price in self.prices},
|
||||
"form_kwargs": {
|
||||
"allowed_prices": {price.id: price for price in self.prices}
|
||||
},
|
||||
}
|
||||
return kwargs
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.customer = get_object_or_404(Customer, user_id=self.kwargs["user_id"])
|
||||
|
||||
@@ -5,10 +5,11 @@ interface BasketItem {
|
||||
name: string;
|
||||
quantity: number;
|
||||
unitPrice: number;
|
||||
isRefill: boolean;
|
||||
}
|
||||
|
||||
const BASKET_CACHE_KEY = "basket";
|
||||
const BASKET_CACHE_VERSION = 1;
|
||||
const BASKET_CACHE_VERSION = 2;
|
||||
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.data("basket", (validPrices: number[], lastPurchaseTime?: number) => ({
|
||||
@@ -21,7 +22,7 @@ document.addEventListener("alpine:init", () => {
|
||||
});
|
||||
document
|
||||
.getElementById("id_form-TOTAL_FORMS")
|
||||
.setAttribute(":value", "basket.length");
|
||||
?.setAttribute(":value", "basket.length");
|
||||
},
|
||||
|
||||
loadBasket(): BasketItem[] {
|
||||
@@ -32,8 +33,8 @@ document.addEventListener("alpine:init", () => {
|
||||
return [];
|
||||
}
|
||||
if (
|
||||
lastPurchaseTime !== null &&
|
||||
localStorage.basketTimestamp !== undefined &&
|
||||
lastPurchaseTime &&
|
||||
localStorage.basketTimestamp &&
|
||||
new Date(lastPurchaseTime) >=
|
||||
new Date(Number.parseInt(localStorage.basketTimestamp, 10))
|
||||
) {
|
||||
@@ -64,6 +65,15 @@ document.addEventListener("alpine:init", () => {
|
||||
);
|
||||
},
|
||||
|
||||
getTotalRefill() {
|
||||
return this.basket
|
||||
.filter((item) => item.isRefill)
|
||||
.reduce(
|
||||
(acc: number, item: BasketItem) => acc + item.quantity * item.unitPrice,
|
||||
0,
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add 1 to the quantity of an item in the basket
|
||||
* @param {BasketItem} item
|
||||
@@ -86,7 +96,7 @@ document.addEventListener("alpine:init", () => {
|
||||
|
||||
if (this.basket[index].quantity === 0) {
|
||||
this.basket = this.basket.filter(
|
||||
(e: BasketItem) => e.priceId !== this.basket[index].id,
|
||||
(e: BasketItem) => e.priceId !== this.basket[index].priceId,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -103,14 +113,16 @@ document.addEventListener("alpine:init", () => {
|
||||
* @param id The id of the product to add
|
||||
* @param name The name of the product
|
||||
* @param price The unit price of the product
|
||||
* @param isRefill true if the product is a refill bond
|
||||
* @returns The created item
|
||||
*/
|
||||
createItem(id: number, name: string, price: number): BasketItem {
|
||||
createItem(id: number, name: string, price: number, isRefill: boolean): BasketItem {
|
||||
const newItem = {
|
||||
priceId: id,
|
||||
name,
|
||||
quantity: 0,
|
||||
unitPrice: price,
|
||||
isRefill,
|
||||
} as BasketItem;
|
||||
|
||||
this.basket.push(newItem);
|
||||
@@ -125,16 +137,17 @@ document.addEventListener("alpine:init", () => {
|
||||
* @param id The id of the product to add
|
||||
* @param name The name of the product
|
||||
* @param price The unit price of the product
|
||||
* @param isRefill true if the product is a refill bond
|
||||
*/
|
||||
addFromCatalog(id: number, name: string, price: number) {
|
||||
let item = this.basket.find((e: BasketItem) => e.priceId === id);
|
||||
addFromCatalog(id: number, name: string, price: number, isRefill: boolean) {
|
||||
const item = this.basket.find((e: BasketItem) => e.priceId === id);
|
||||
|
||||
// if the item is not in the basket, we create it
|
||||
// else we add + 1 to it
|
||||
if (item) {
|
||||
this.add(item);
|
||||
} else {
|
||||
item = this.createItem(id, name, price);
|
||||
this.createItem(id, name, price, isRefill);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -58,6 +58,17 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<template x-if="(getTotalRefill() + {{ customer_amount }}) > {{ settings.SITH_ACCOUNT_MAX_MONEY }}">
|
||||
<div class="alert alert-red">
|
||||
<div class="alert-main">
|
||||
{% trans trimmed limit=settings.SITH_ACCOUNT_MAX_MONEY %}
|
||||
You cannot purchase the current basket,
|
||||
because it would put your AE account balance
|
||||
above the {{ limit }}€ limit
|
||||
{% endtrans %}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ul class="item-list">
|
||||
{# Starting money #}
|
||||
<li>
|
||||
@@ -109,9 +120,12 @@
|
||||
<i class="fa fa-trash"></i>
|
||||
{% trans %}Clear{% endtrans %}
|
||||
</button>
|
||||
<button class="btn btn-blue">
|
||||
<button
|
||||
class="btn btn-blue"
|
||||
:disabled="(getTotalRefill() + {{ customer_amount }}) > {{ settings.SITH_ACCOUNT_MAX_MONEY }}"
|
||||
>
|
||||
<i class="fa fa-check"></i>
|
||||
<input type="submit" value="{% trans %}Validate{% endtrans %}"/>
|
||||
{% trans %}Validate{% endtrans %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -199,7 +213,12 @@
|
||||
id="{{ price.id }}"
|
||||
class="card clickable shadow"
|
||||
:class="{selected: basket.some((i) => i.priceId === {{ price.id }})}"
|
||||
@click='addFromCatalog({{ price.id }}, {{ price.full_label|tojson }}, {{ price.amount }})'
|
||||
@click='addFromCatalog(
|
||||
{{ price.id }},
|
||||
{{ price.full_label|tojson }},
|
||||
{{ price.amount }},
|
||||
{{ (price.product.product_type_id == settings.SITH_COUNTER_PRODUCTTYPE_REFILLING)|lower }}
|
||||
)'
|
||||
{% if price.sold_out %}disabled{% endif %}
|
||||
>
|
||||
{% if price.product.icon %}
|
||||
|
||||
+25
-5
@@ -70,6 +70,26 @@ class BaseEbouticBasketForm(BaseBasketForm):
|
||||
# Disable money check
|
||||
...
|
||||
|
||||
def _check_refills(self):
|
||||
"""Check that this basket won't put customer balance above the limit."""
|
||||
refill_type_id = settings.SITH_COUNTER_PRODUCTTYPE_REFILLING
|
||||
total_refill = sum(
|
||||
f.price.amount * f.cleaned_data["quantity"]
|
||||
for f in self.forms
|
||||
if f.price.product.product_type_id == refill_type_id
|
||||
)
|
||||
total_other = sum(
|
||||
f.price.amount * f.cleaned_data["quantity"]
|
||||
for f in self.forms
|
||||
if f.price.product.product_type_id != refill_type_id
|
||||
)
|
||||
limit = settings.SITH_ACCOUNT_MAX_MONEY
|
||||
if (total_refill - total_other + self.customer.amount) > limit:
|
||||
raise ValidationError(
|
||||
_("There cannot be more than %(money)d€ on an AE account")
|
||||
% {"money": limit}
|
||||
)
|
||||
|
||||
|
||||
EbouticBasketForm = forms.formset_factory(
|
||||
BasketItemForm, formset=BaseEbouticBasketForm, absolute_max=None, min_num=1
|
||||
@@ -88,15 +108,15 @@ class EbouticMainView(LoginRequiredMixin, FormView):
|
||||
form_class = EbouticBasketForm
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs["form_kwargs"] = {
|
||||
return super().get_form_kwargs() | {
|
||||
"customer": self.customer,
|
||||
"counter": get_eboutic(),
|
||||
"allowed_prices": {
|
||||
price.id: price for price in self.prices if not price.sold_out
|
||||
"form_kwargs": {
|
||||
"allowed_prices": {
|
||||
price.id: price for price in self.prices if not price.sold_out
|
||||
}
|
||||
},
|
||||
}
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, formset):
|
||||
if len(formset) == 0:
|
||||
|
||||
Reference in New Issue
Block a user