From e9361697f74f05fc1659c88a9151aa9e03518c25 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 15 Dec 2024 21:33:19 +0100 Subject: [PATCH 1/9] Convert customer refill to a fragment view --- counter/api.py | 12 ++- counter/schemas.py | 8 +- .../counter/counter-click-index.ts} | 0 counter/templates/counter/counter_click.jinja | 30 +++--- .../counter/fragments/create_refill.jinja | 9 ++ counter/tests/test_counter.py | 31 +++++-- counter/urls.py | 7 +- counter/views/click.py | 93 +++++++++++++++---- counter/views/student_card.py | 10 +- 9 files changed, 148 insertions(+), 52 deletions(-) rename counter/static/{counter/js/counter_click.js => bundled/counter/counter-click-index.ts} (100%) create mode 100644 counter/templates/counter/fragments/create_refill.jinja diff --git a/counter/api.py b/counter/api.py index f3f0f101..d58f0154 100644 --- a/counter/api.py +++ b/counter/api.py @@ -21,11 +21,12 @@ from ninja_extra import ControllerBase, api_controller, paginate, route from ninja_extra.pagination import PageNumberPaginationExtra from ninja_extra.schemas import PaginatedResponseSchema -from core.api_permissions import CanAccessLookup, CanView, IsRoot -from counter.models import Counter, Product +from core.api_permissions import CanAccessLookup, CanView, IsLoggedInCounter, IsRoot +from counter.models import Counter, Customer, Product from counter.schemas import ( CounterFilterSchema, CounterSchema, + CustomerBalance, ProductSchema, SimplifiedCounterSchema, ) @@ -60,6 +61,13 @@ class CounterController(ControllerBase): return filters.filter(Counter.objects.all()) +@api_controller("/customer") +class CustomerController(ControllerBase): + @route.get("/balance", response=CustomerBalance, permissions=[IsLoggedInCounter]) + def get_balance(self, customer_id: int): + return self.get_object_or_exception(Customer, pk=customer_id) + + @api_controller("/product") class ProductController(ControllerBase): @route.get( diff --git a/counter/schemas.py b/counter/schemas.py index ec1a842d..4fbbc712 100644 --- a/counter/schemas.py +++ b/counter/schemas.py @@ -4,7 +4,7 @@ from annotated_types import MinLen from ninja import Field, FilterSchema, ModelSchema from core.schemas import SimpleUserSchema -from counter.models import Counter, Product +from counter.models import Counter, Customer, Product class CounterSchema(ModelSchema): @@ -16,6 +16,12 @@ class CounterSchema(ModelSchema): fields = ["id", "name", "type", "club", "products"] +class CustomerBalance(ModelSchema): + class Meta: + model = Customer + fields = ["amount"] + + class CounterFilterSchema(FilterSchema): search: Annotated[str, MinLen(1)] = Field(None, q="name__icontains") diff --git a/counter/static/counter/js/counter_click.js b/counter/static/bundled/counter/counter-click-index.ts similarity index 100% rename from counter/static/counter/js/counter_click.js rename to counter/static/bundled/counter/counter-click-index.ts diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index 3df77555..65c7d9ca 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -6,7 +6,7 @@ {% endblock %} {% block additional_js %} - + {% endblock %} {% block info_boxes %} @@ -28,7 +28,7 @@
{% trans %}Customer{% endtrans %}
{{ user_mini_profile(customer.user) }} {{ user_subscription(customer.user) }} -

{% trans %}Amount: {% endtrans %}{{ customer.amount }} €

+

{% trans %}Amount: {% endtrans %}

{% if counter.type == 'BAR' %}
{% trans %}Student card{% endtrans %}
@@ -105,16 +105,12 @@ - {% if (counter.type == 'BAR' and barmens_can_refill) %} + {% if refilling_fragment %}
{% trans %}Refilling{% endtrans %}
-
-
- {% csrf_token %} - {{ refill_form.as_p() }} - - -
+
+ {{ refilling_fragment }}
{% endif %}
@@ -155,9 +151,6 @@ {% block script %} {{ super() }} {% endblock script %} \ No newline at end of file diff --git a/counter/templates/counter/fragments/create_refill.jinja b/counter/templates/counter/fragments/create_refill.jinja new file mode 100644 index 00000000..7de612a3 --- /dev/null +++ b/counter/templates/counter/fragments/create_refill.jinja @@ -0,0 +1,9 @@ +
+ {% csrf_token %} + {{ form.as_p() }} + +
diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index 855b43f6..2574865f 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -67,17 +67,24 @@ class TestCounter(TestCase): {"code": self.richard.customer.account_id, "counter_token": counter_token}, ) counter_url = response.get("location") - response = self.client.get(response.get("location")) + refill_url = reverse( + "counter:refilling_create", + kwargs={"customer_id": self.richard.customer.pk}, + ) + + response = self.client.get( + response.get("location"), + ) assert ">Richard Batsbak/", CounterMain.as_view(), name="details"), path("/click//", CounterClick.as_view(), name="click"), + path( + "refill//", + RefillingCreateView.as_view(), + name="refilling_create", + ), path( "/last_ops/", CounterLastOperationsView.as_view(), diff --git a/counter/views/click.py b/counter/views/click.py index 65e889ff..1d87ac8b 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -24,11 +24,13 @@ from django.http import Http404, HttpResponseRedirect, JsonResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView +from django.views.generic import DetailView, FormView +from core.utils import FormFragmentTemplateData from core.views import CanViewMixin from counter.forms import RefillForm from counter.models import Counter, Customer, Product, Selling +from counter.utils import is_logged_in_counter from counter.views.mixins import CounterTabsMixin from counter.views.student_card import StudentCardFormView @@ -100,7 +102,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["too_young"] = False request.session["not_allowed"] = False request.session["no_age"] = False - self.refill_form = None ret = super().get(request, *args, **kwargs) if (self.object.type != "BAR" and not request.user.is_authenticated) or ( self.object.type == "BAR" and len(self.object.barmen_list) == 0 @@ -111,7 +112,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): def post(self, request, *args, **kwargs): """Handle the many possibilities of the post request.""" self.object = self.get_object() - self.refill_form = None if (self.object.type != "BAR" and not request.user.is_authenticated) or ( self.object.type == "BAR" and len(self.object.barmen_list) < 1 ): # Check that at least one barman is logged in @@ -148,8 +148,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): self.add_product(request) elif action == "del_product": self.del_product(request) - elif action == "refill": - self.refill(request) elif action == "code": return self.parse_code(request) elif action == "cancel": @@ -383,19 +381,6 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): reverse_lazy("counter:details", args=self.args, kwargs=kwargs) ) - def refill(self, request): - """Refill the customer's account.""" - if not self.object.can_refill(): - raise PermissionDenied - form = RefillForm(request.POST) - if form.is_valid(): - form.instance.counter = self.object - form.instance.operator = self.operator - form.instance.customer = self.customer - form.instance.save() - else: - self.refill_form = form - def get_context_data(self, **kwargs): """Add customer to the context.""" kwargs = super().get_context_data(**kwargs) @@ -413,9 +398,77 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): ) kwargs["customer"] = self.customer kwargs["basket_total"] = self.sum_basket(self.request) - kwargs["refill_form"] = self.refill_form or RefillForm() - kwargs["barmens_can_refill"] = self.object.can_refill() kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( self.customer ).render(self.request) + + if self.object.can_refill(): + kwargs["refilling_fragment"] = RefillingCreateView.get_template_data( + self.customer + ).render(self.request) return kwargs + + +class RefillingCreateView(FormView): + """This is a fragment only view which integrates with counter_click.jinja""" + + form_class = RefillForm + template_name = "counter/fragments/create_refill.jinja" + + @classmethod + def get_template_data( + cls, customer: Customer, *, form_instance: form_class | None = None + ) -> FormFragmentTemplateData[form_class]: + return FormFragmentTemplateData[cls.form_class]( + form=form_instance if form_instance else cls.form_class(), + template=cls.template_name, + context={ + "action": reverse_lazy( + "counter:refilling_create", kwargs={"customer_id": customer.pk} + ), + }, + ) + + def dispatch(self, request, *args, **kwargs): + self.customer: Customer = get_object_or_404(Customer, pk=kwargs["customer_id"]) + if not self.customer.can_buy: + raise Http404 + + if not is_logged_in_counter(request): + raise PermissionDenied + + self.counter: Counter = get_object_or_404( + Counter, token=request.session["counter_token"] + ) + + if not self.counter.can_refill(): + raise PermissionDenied + + if self.customer_is_barman(): + self.operator = self.customer.user + else: + self.operator = self.counter.get_random_barman() + + return super().dispatch(request, *args, **kwargs) + + def customer_is_barman(self) -> bool: + barmen = self.counter.barmen_list + return self.counter.type == "BAR" and self.customer.user in barmen + + def form_valid(self, form): + res = super().form_valid(form) + form.clean() + form.instance.counter = self.counter + form.instance.operator = self.operator + form.instance.customer = self.customer + form.instance.save() + return res + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + data = self.get_template_data(self.customer, form_instance=context["form"]) + context.update(data.context) + return context + + def get_success_url(self, **kwargs): + return self.request.path diff --git a/counter/views/student_card.py b/counter/views/student_card.py index 35226e95..952f65eb 100644 --- a/counter/views/student_card.py +++ b/counter/views/student_card.py @@ -70,11 +70,11 @@ class StudentCardFormView(FormView): @classmethod def get_template_data( - cls, customer: Customer - ) -> FormFragmentTemplateData[StudentCardForm]: + cls, customer: Customer, *, form_instance: form_class | None = None + ) -> FormFragmentTemplateData[form_class]: """Get necessary data to pre-render the fragment""" - return FormFragmentTemplateData( - form=cls.form_class(), + return FormFragmentTemplateData[cls.form_class]( + form=form_instance if form_instance else cls.form_class(), template=cls.template_name, context={ "action": reverse( @@ -105,7 +105,7 @@ class StudentCardFormView(FormView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - data = self.get_template_data(self.customer) + data = self.get_template_data(self.customer, form_instance=context["form"]) context.update(data.context) return context From cde864fdc7d7dcb9fe480a60e525aea0ff86c448 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 15 Dec 2024 21:33:43 +0100 Subject: [PATCH 2/9] Apply review comments --- counter/api.py | 11 +- counter/models.py | 9 ++ counter/schemas.py | 4 +- .../bundled/counter/counter-click-index.ts | 149 ++++++++++++------ counter/tests/test_api.py | 105 ++++++++++++ counter/views/click.py | 18 +-- 6 files changed, 226 insertions(+), 70 deletions(-) create mode 100644 counter/tests/test_api.py diff --git a/counter/api.py b/counter/api.py index d58f0154..e51aea26 100644 --- a/counter/api.py +++ b/counter/api.py @@ -26,7 +26,7 @@ from counter.models import Counter, Customer, Product from counter.schemas import ( CounterFilterSchema, CounterSchema, - CustomerBalance, + CustomerSchema, ProductSchema, SimplifiedCounterSchema, ) @@ -63,8 +63,13 @@ class CounterController(ControllerBase): @api_controller("/customer") class CustomerController(ControllerBase): - @route.get("/balance", response=CustomerBalance, permissions=[IsLoggedInCounter]) - def get_balance(self, customer_id: int): + @route.get( + "{customer_id}", + response=CustomerSchema, + permissions=[IsLoggedInCounter], + url_name="get_customer", + ) + def get_customer(self, customer_id: int): return self.get_object_or_exception(Customer, pk=customer_id) diff --git a/counter/models.py b/counter/models.py index 292cd59f..da2f33f4 100644 --- a/counter/models.py +++ b/counter/models.py @@ -650,6 +650,15 @@ class Counter(models.Model): ) )["total"] + def customer_is_barman(self, customer: Customer | User) -> bool: + """Check if current counter is a `bar` and that the customer is on the barmen_list + + This is useful to compute special prices""" + if isinstance(customer, Customer): + customer: User = customer.user + + return self.type == "BAR" and customer in self.barmen_list + class RefillingQuerySet(models.QuerySet): def annotate_total(self) -> Self: diff --git a/counter/schemas.py b/counter/schemas.py index 4fbbc712..7fbe1a71 100644 --- a/counter/schemas.py +++ b/counter/schemas.py @@ -16,10 +16,10 @@ class CounterSchema(ModelSchema): fields = ["id", "name", "type", "club", "products"] -class CustomerBalance(ModelSchema): +class CustomerSchema(ModelSchema): class Meta: model = Customer - fields = ["amount"] + fields = ["user", "account_id", "amount", "recorded_products"] class CounterFilterSchema(FilterSchema): diff --git a/counter/static/bundled/counter/counter-click-index.ts b/counter/static/bundled/counter/counter-click-index.ts index b0ddb42c..01d7e8f9 100644 --- a/counter/static/bundled/counter/counter-click-index.ts +++ b/counter/static/bundled/counter/counter-click-index.ts @@ -1,74 +1,117 @@ -document.addEventListener("alpine:init", () => { - Alpine.data("counter", () => ({ - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - basket: sessionBasket, - errors: [], +import { exportToHtml } from "#core:utils/globals"; +import { customerGetCustomer } from "#openapi"; - sumBasket() { - if (!this.basket || Object.keys(this.basket).length === 0) { - return 0; - } - const total = Object.values(this.basket).reduce( - (acc, cur) => acc + cur.qty * cur.price, - 0, - ); - return total / 100; - }, +interface CounterConfig { + csrfToken: string; + clickApiUrl: string; + sessionBasket: Record; + customerBalance: number; + customerId: number; +} +interface BasketItem { + // biome-ignore lint/style/useNamingConvention: talking with python + bonus_qty: number; + price: number; + qty: number; +} - async handleCode(event) { - const code = $(event.target).find("#code_field").val().toUpperCase(); - if (["FIN", "ANN"].includes(code)) { - $(event.target).submit(); - } else { - await this.handleAction(event); - } - }, +exportToHtml("loadCounter", (config: CounterConfig) => { + document.addEventListener("alpine:init", () => { + Alpine.data("counter", () => ({ + basket: config.sessionBasket, + errors: [], + customerBalance: config.customerBalance, - async handleAction(event) { - const payload = $(event.target).serialize(); - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - const request = new Request(clickApiUrl, { - method: "POST", - body: payload, - headers: { - // biome-ignore lint/style/useNamingConvention: this goes into http headers - Accept: "application/json", - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - "X-CSRFToken": csrfToken, - }, - }); - const response = await fetch(request); - const json = await response.json(); - this.basket = json.basket; - this.errors = json.errors; - $("form.code_form #code_field").val("").focus(); - }, - })); + sumBasket() { + if (!this.basket || Object.keys(this.basket).length === 0) { + return 0; + } + const total = Object.values(this.basket).reduce( + (acc: number, cur: BasketItem) => acc + cur.qty * cur.price, + 0, + ) as number; + return total / 100; + }, + + async updateBalance() { + this.customerBalance = ( + await customerGetCustomer({ + path: { + // biome-ignore lint/style/useNamingConvention: api is in snake_case + customer_id: config.customerId, + }, + }) + ).data.amount; + }, + + async handleCode(event: SubmitEvent) { + const code = ( + $(event.target).find("#code_field").val() as string + ).toUpperCase(); + if (["FIN", "ANN"].includes(code)) { + $(event.target).submit(); + } else { + await this.handleAction(event); + } + }, + + async handleAction(event: SubmitEvent) { + const payload = $(event.target).serialize(); + const request = new Request(config.clickApiUrl, { + method: "POST", + body: payload, + headers: { + // biome-ignore lint/style/useNamingConvention: this goes into http headers + Accept: "application/json", + "X-CSRFToken": config.csrfToken, + }, + }); + const response = await fetch(request); + const json = await response.json(); + this.basket = json.basket; + this.errors = json.errors; + $("form.code_form #code_field").val("").focus(); + }, + })); + }); }); +interface Product { + value: string; + label: string; + tags: string; +} +declare global { + const productsAutocomplete: Product[]; +} + $(() => { /* Autocompletion in the code field */ - const codeField = $("#code_field"); + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + const codeField: any = $("#code_field"); let quantity = ""; codeField.autocomplete({ - select: (event, ui) => { + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + select: (event: any, ui: any) => { event.preventDefault(); codeField.val(quantity + ui.item.value); }, - focus: (event, ui) => { + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + focus: (event: any, ui: any) => { event.preventDefault(); codeField.val(quantity + ui.item.value); }, - source: (request, response) => { + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + source: (request: any, response: any) => { // biome-ignore lint/performance/useTopLevelRegex: performance impact is minimal const res = /^(\d+x)?(.*)/i.exec(request.term); quantity = res[1] || ""; const search = res[2]; - const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i"); + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + const matcher = new RegExp(($ as any).ui.autocomplete.escapeRegex(search), "i"); response( - // biome-ignore lint/correctness/noUndeclaredVariables: defined in counter_click.jinja - $.grep(productsAutocomplete, (value) => { + $.grep(productsAutocomplete, (value: Product) => { return matcher.test(value.tags); }), ); @@ -76,11 +119,13 @@ $(() => { }); /* Accordion UI between basket and refills */ - $("#click_form").accordion({ + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + ($("#click_form") as any).accordion({ heightStyle: "content", activate: () => $(".focus").focus(), }); - $("#products").tabs(); + // biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery + ($("#products") as any).tabs(); codeField.focus(); }); diff --git a/counter/tests/test_api.py b/counter/tests/test_api.py new file mode 100644 index 00000000..8a5efdc1 --- /dev/null +++ b/counter/tests/test_api.py @@ -0,0 +1,105 @@ +import pytest +from django.contrib.auth.models import make_password +from django.test.client import Client +from django.urls import reverse +from model_bakery import baker + +from core.baker_recipes import board_user, subscriber_user +from core.models import User +from counter.models import Counter + + +@pytest.fixture +def customer_user() -> User: + return subscriber_user.make() + + +@pytest.fixture +def counter_bar() -> Counter: + return baker.make(Counter, type="BAR") + + +@pytest.fixture +def barmen(counter_bar: Counter) -> User: + user = subscriber_user.make(password=make_password("plop")) + counter_bar.sellers.add(user) + return user + + +@pytest.fixture +def board_member() -> User: + return board_user.make() + + +@pytest.fixture +def root_user() -> User: + return baker.make(User, is_superuser=True) + + +@pytest.mark.django_db +@pytest.mark.parametrize( + ("connected_user"), + [ + None, # Anonymous user + "barmen", + "customer_user", + "board_member", + "root_user", + ], +) +def test_get_customer_fail( + client: Client, + customer_user: User, + request: pytest.FixtureRequest, + connected_user: str | None, +): + if connected_user is not None: + client.force_login(request.getfixturevalue(connected_user)) + assert ( + client.get( + reverse("api:get_customer", kwargs={"customer_id": customer_user.id}) + ).status_code + == 403 + ) + + +@pytest.mark.django_db +def test_get_customer_from_bar_fail_wrong_referrer( + client: Client, customer_user: User, barmen: User, counter_bar: Counter +): + client.post( + reverse("counter:login", args=[counter_bar.pk]), + {"username": barmen.username, "password": "plop"}, + ) + + assert ( + client.get( + reverse("api:get_customer", kwargs={"customer_id": customer_user.id}) + ).status_code + == 403 + ) + + +@pytest.mark.django_db +def test_get_customer_from_bar_success( + client: Client, customer_user: User, barmen: User, counter_bar: Counter +): + client.post( + reverse("counter:login", args=[counter_bar.pk]), + {"username": barmen.username, "password": "plop"}, + ) + + response = client.get( + reverse("api:get_customer", kwargs={"customer_id": customer_user.id}), + HTTP_REFERER=reverse( + "counter:click", + kwargs={"counter_id": counter_bar.id, "user_id": customer_user.id}, + ), + ) + assert response.status_code == 200 + assert response.json() == { + "user": customer_user.id, + "account_id": customer_user.customer.account_id, + "amount": f"{customer_user.customer.amount:.2f}", + "recorded_products": customer_user.customer.recorded_products, + } diff --git a/counter/views/click.py b/counter/views/click.py index 1d87ac8b..6a379564 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -137,7 +137,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): request.session["no_age"] = False if self.object.type != "BAR": self.operator = request.user - elif self.customer_is_barman(): + elif self.object.customer_is_barman(self.customer): self.operator = self.customer.user else: self.operator = self.object.get_random_barman() @@ -157,16 +157,12 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): context = self.get_context_data(object=self.object) return self.render_to_response(context) - def customer_is_barman(self) -> bool: - barmen = self.object.barmen_list - return self.object.type == "BAR" and self.customer.user in barmen - def get_product(self, pid): return Product.objects.filter(pk=int(pid)).first() def get_price(self, pid): p = self.get_product(pid) - if self.customer_is_barman(): + if self.object.customer_is_barman(self.customer): price = p.special_selling_price else: price = p.selling_price @@ -331,7 +327,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): for pid, infos in request.session["basket"].items(): # This duplicates code for DB optimization (prevent to load many times the same object) p = Product.objects.filter(pk=pid).first() - if self.customer_is_barman(): + if self.object.customer_is_barman(self.customer): uprice = p.special_selling_price else: uprice = p.selling_price @@ -385,7 +381,7 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): """Add customer to the context.""" kwargs = super().get_context_data(**kwargs) products = self.object.products.select_related("product_type") - if self.customer_is_barman(): + if self.object.customer_is_barman(self.customer): products = products.annotate(price=F("special_selling_price")) else: products = products.annotate(price=F("selling_price")) @@ -444,17 +440,13 @@ class RefillingCreateView(FormView): if not self.counter.can_refill(): raise PermissionDenied - if self.customer_is_barman(): + if self.counter.customer_is_barman(self.customer): self.operator = self.customer.user else: self.operator = self.counter.get_random_barman() return super().dispatch(request, *args, **kwargs) - def customer_is_barman(self) -> bool: - barmen = self.counter.barmen_list - return self.counter.type == "BAR" and self.customer.user in barmen - def form_valid(self, form): res = super().form_valid(form) form.clean() From f63fb59cbfd7ed927efb778700fc24f03d7aa206 Mon Sep 17 00:00:00 2001 From: Sli Date: Mon, 16 Dec 2024 00:15:21 +0100 Subject: [PATCH 3/9] Allow filtering of refilling options * Move settings.SITH_COUNTER_PAYMENT_METHOD to counter.apps.PAYMENT_METHOD * Move student cards to an accordion on counter click * Make cash default refilling option * Disable bank selection option in refilling if CHECK are not allowed * Disable refilling with CHECK from the frontend --- counter/apps.py | 6 + counter/forms.py | 17 + .../0027_alter_refilling_payment_method.py | 22 + counter/models.py | 5 +- counter/templates/counter/counter_click.jinja | 11 +- counter/views/click.py | 8 +- locale/fr/LC_MESSAGES/django.po | 397 +++++++++--------- sith/settings.py | 6 - subscription/views.py | 3 +- 9 files changed, 260 insertions(+), 215 deletions(-) create mode 100644 counter/migrations/0027_alter_refilling_payment_method.py diff --git a/counter/apps.py b/counter/apps.py index 54e7ad4c..9348cd1c 100644 --- a/counter/apps.py +++ b/counter/apps.py @@ -24,6 +24,12 @@ from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ +PAYMENT_METHOD = [ + ("CHECK", _("Check")), + ("CASH", _("Cash")), + ("CARD", _("Credit card")), +] + class CounterConfig(AppConfig): name = "counter" diff --git a/counter/forms.py b/counter/forms.py index 0a8bb3be..80a0e0ad 100644 --- a/counter/forms.py +++ b/counter/forms.py @@ -111,6 +111,8 @@ class GetUserForm(forms.Form): class RefillForm(forms.ModelForm): + allowed_refilling_methods = ["CASH", "CARD"] + error_css_class = "error" required_css_class = "required" amount = forms.FloatField( @@ -120,6 +122,21 @@ class RefillForm(forms.ModelForm): class Meta: model = Refilling fields = ["amount", "payment_method", "bank"] + widgets = {"payment_method": forms.RadioSelect} + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields["payment_method"].choices = ( + method + for method in self.fields["payment_method"].choices + if method[0] in self.allowed_refilling_methods + ) + if self.fields["payment_method"].initial not in self.allowed_refilling_methods: + self.fields["payment_method"].initial = self.allowed_refilling_methods[0] + + if "CHECK" not in self.allowed_refilling_methods: + del self.fields["bank"] class CounterEditForm(forms.ModelForm): diff --git a/counter/migrations/0027_alter_refilling_payment_method.py b/counter/migrations/0027_alter_refilling_payment_method.py new file mode 100644 index 00000000..af9dd54a --- /dev/null +++ b/counter/migrations/0027_alter_refilling_payment_method.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.17 on 2024-12-15 22:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("counter", "0026_alter_studentcard_customer"), + ] + + operations = [ + migrations.AlterField( + model_name="refilling", + name="payment_method", + field=models.CharField( + choices=[("CHECK", "Check"), ("CASH", "Cash"), ("CARD", "Credit card")], + default="CARD", + max_length=255, + verbose_name="payment method", + ), + ), + ] diff --git a/counter/models.py b/counter/models.py index da2f33f4..3640c3f5 100644 --- a/counter/models.py +++ b/counter/models.py @@ -42,6 +42,7 @@ from club.models import Club from core.fields import ResizedImageField from core.models import Group, Notification, User from core.utils import get_start_of_semester +from counter.apps import PAYMENT_METHOD from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB from subscription.models import Subscription @@ -697,8 +698,8 @@ class Refilling(models.Model): payment_method = models.CharField( _("payment method"), max_length=255, - choices=settings.SITH_COUNTER_PAYMENT_METHOD, - default="CASH", + choices=PAYMENT_METHOD, + default="CARD", ) bank = models.CharField( _("bank"), max_length=255, choices=settings.SITH_COUNTER_BANK, default="OTHER" diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index 65c7d9ca..bc5c4e58 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -29,11 +29,6 @@ {{ user_mini_profile(customer.user) }} {{ user_subscription(customer.user) }}

{% trans %}Amount: {% endtrans %}

- - {% if counter.type == 'BAR' %} -
{% trans %}Student card{% endtrans %}
- {{ student_card_fragment }} - {% endif %}
@@ -113,6 +108,12 @@ {{ refilling_fragment }}
{% endif %} + {% if student_card_fragment %} +
{% trans %}Student card{% endtrans %}
+
+ {{ student_card_fragment }} +
+ {% endif %}
diff --git a/counter/views/click.py b/counter/views/click.py index 6a379564..c6bafa57 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -394,9 +394,11 @@ class CounterClick(CounterTabsMixin, CanViewMixin, DetailView): ) kwargs["customer"] = self.customer kwargs["basket_total"] = self.sum_basket(self.request) - kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( - self.customer - ).render(self.request) + + if self.object.type == "BAR": + kwargs["student_card_fragment"] = StudentCardFormView.get_template_data( + self.customer + ).render(self.request) if self.object.can_refill(): kwargs["refilling_fragment"] = RefillingCreateView.get_template_data( diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 482befd3..dd9aef28 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-11 09:34+0100\n" +"POT-Creation-Date: 2024-12-16 00:11+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -18,8 +18,8 @@ msgstr "" #: accounting/models.py:62 accounting/models.py:101 accounting/models.py:132 #: accounting/models.py:190 club/models.py:55 com/models.py:274 -#: com/models.py:293 counter/models.py:297 counter/models.py:328 -#: counter/models.py:479 forum/models.py:60 launderette/models.py:29 +#: com/models.py:293 counter/models.py:298 counter/models.py:329 +#: counter/models.py:480 forum/models.py:60 launderette/models.py:29 #: launderette/models.py:80 launderette/models.py:116 msgid "name" msgstr "nom" @@ -65,8 +65,8 @@ msgid "account number" msgstr "numéro de compte" #: accounting/models.py:107 accounting/models.py:136 club/models.py:345 -#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:357 -#: counter/models.py:481 trombi/models.py:209 +#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:358 +#: counter/models.py:482 trombi/models.py:209 msgid "club" msgstr "club" @@ -87,12 +87,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:188 club/models.py:351 counter/models.py:959 +#: accounting/models.py:188 club/models.py:351 counter/models.py:969 #: election/models.py:16 launderette/models.py:165 msgid "start date" msgstr "date de début" -#: accounting/models.py:189 club/models.py:352 counter/models.py:960 +#: accounting/models.py:189 club/models.py:352 counter/models.py:970 #: election/models.py:17 msgid "end date" msgstr "date de fin" @@ -105,8 +105,8 @@ msgstr "est fermé" msgid "club account" msgstr "compte club" -#: accounting/models.py:199 accounting/models.py:255 counter/models.py:91 -#: counter/models.py:677 +#: accounting/models.py:199 accounting/models.py:255 counter/models.py:92 +#: counter/models.py:687 msgid "amount" msgstr "montant" @@ -128,18 +128,18 @@ msgstr "classeur" #: accounting/models.py:256 core/models.py:956 core/models.py:1467 #: core/models.py:1512 core/models.py:1541 core/models.py:1565 -#: counter/models.py:687 counter/models.py:791 counter/models.py:995 +#: counter/models.py:697 counter/models.py:801 counter/models.py:1005 #: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:312 #: forum/models.py:413 msgid "date" msgstr "date" -#: accounting/models.py:257 counter/models.py:299 counter/models.py:996 +#: accounting/models.py:257 counter/models.py:300 counter/models.py:1006 #: pedagogy/models.py:208 msgid "comment" msgstr "commentaire" -#: accounting/models.py:259 counter/models.py:689 counter/models.py:793 +#: accounting/models.py:259 counter/models.py:699 counter/models.py:803 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" @@ -166,7 +166,7 @@ msgstr "type comptable" #: accounting/models.py:294 accounting/models.py:429 accounting/models.py:460 #: accounting/models.py:492 core/models.py:1540 core/models.py:1566 -#: counter/models.py:757 +#: counter/models.py:767 msgid "label" msgstr "étiquette" @@ -264,7 +264,7 @@ msgstr "" "Vous devez fournir soit un type comptable simplifié ou un type comptable " "standard" -#: accounting/models.py:421 counter/models.py:338 pedagogy/models.py:41 +#: accounting/models.py:421 counter/models.py:339 pedagogy/models.py:41 msgid "code" msgstr "code" @@ -369,7 +369,7 @@ msgstr "Compte en banque : " #: core/templates/core/user_clubs.jinja:34 #: core/templates/core/user_clubs.jinja:63 #: core/templates/core/user_edit.jinja:62 -#: counter/templates/counter/fragments/create_student_card.jinja:22 +#: counter/templates/counter/fragments/create_student_card.jinja:25 #: counter/templates/counter/last_ops.jinja:35 #: counter/templates/counter/last_ops.jinja:65 #: election/templates/election/election_detail.jinja:191 @@ -517,7 +517,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:469 +#: sith/settings.py:463 msgid "Closed" msgstr "Fermé" @@ -950,11 +950,11 @@ msgstr "Une action est requise" msgid "You must specify at least an user or an email address" msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" -#: club/forms.py:149 counter/forms.py:189 +#: club/forms.py:149 counter/forms.py:206 msgid "Begin date" msgstr "Date de début" -#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:192 +#: club/forms.py:152 com/views.py:84 com/views.py:202 counter/forms.py:209 #: election/views.py:170 subscription/forms.py:21 msgid "End date" msgstr "Date de fin" @@ -1041,7 +1041,7 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs" msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:337 counter/models.py:950 counter/models.py:986 +#: club/models.py:337 counter/models.py:960 counter/models.py:996 #: eboutic/models.py:53 eboutic/models.py:189 election/models.py:183 #: launderette/models.py:130 launderette/models.py:184 sas/models.py:273 #: trombi/models.py:205 @@ -1053,8 +1053,8 @@ msgstr "nom d'utilisateur" msgid "role" msgstr "rôle" -#: club/models.py:359 core/models.py:90 counter/models.py:298 -#: counter/models.py:329 election/models.py:13 election/models.py:115 +#: club/models.py:359 core/models.py:90 counter/models.py:299 +#: counter/models.py:330 election/models.py:13 election/models.py:115 #: election/models.py:188 forum/models.py:61 forum/models.py:245 msgid "description" msgstr "description" @@ -2501,7 +2501,7 @@ msgstr "Forum" msgid "Gallery" msgstr "Photos" -#: core/templates/core/base/navbar.jinja:22 counter/models.py:489 +#: core/templates/core/base/navbar.jinja:22 counter/models.py:490 #: counter/templates/counter/counter_list.jinja:11 #: eboutic/templates/eboutic/eboutic_main.jinja:4 #: eboutic/templates/eboutic/eboutic_main.jinja:22 @@ -2587,7 +2587,7 @@ msgstr "Confirmation" #: core/templates/core/delete_confirm.jinja:20 #: core/templates/core/file_delete_confirm.jinja:46 -#: counter/templates/counter/counter_click.jinja:104 +#: counter/templates/counter/counter_click.jinja:100 #: counter/templates/counter/fragments/delete_student_card.jinja:12 #: sas/templates/sas/ask_picture_removal.jinja:20 msgid "Cancel" @@ -3300,7 +3300,12 @@ msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s." msgid "Go to my Trombi tools" msgstr "Allez à mes outils de Trombi" -#: core/templates/core/user_preferences.jinja:41 +#: core/templates/core/user_preferences.jinja:39 +#: counter/templates/counter/counter_click.jinja:112 +msgid "Student card" +msgstr "Carte étudiante" + +#: core/templates/core/user_preferences.jinja:42 msgid "" "You can add a card by asking at a counter or add it yourself here. If you " "want to manually\n" @@ -3374,7 +3379,7 @@ msgstr "Cotisations" msgid "Subscription stats" msgstr "Statistiques de cotisation" -#: core/templates/core/user_tools.jinja:48 counter/forms.py:162 +#: core/templates/core/user_tools.jinja:48 counter/forms.py:179 #: counter/views/mixins.py:89 msgid "Counters" msgstr "Comptoirs" @@ -3594,8 +3599,21 @@ msgstr "Photos" msgid "Galaxy" msgstr "Galaxie" -#: counter/apps.py:30 counter/models.py:505 counter/models.py:956 -#: counter/models.py:992 launderette/models.py:32 +#: counter/apps.py:28 sith/settings.py:412 sith/settings.py:419 +msgid "Check" +msgstr "Chèque" + +#: counter/apps.py:29 sith/settings.py:413 sith/settings.py:421 +msgid "Cash" +msgstr "Espèces" + +#: counter/apps.py:30 counter/models.py:805 sith/settings.py:415 +#: sith/settings.py:420 +msgid "Credit card" +msgstr "Carte bancaire" + +#: counter/apps.py:36 counter/models.py:506 counter/models.py:966 +#: counter/models.py:1002 launderette/models.py:32 msgid "counter" msgstr "comptoir" @@ -3619,185 +3637,180 @@ msgstr "Vidange de votre compte AE" msgid "Ecocup regularization" msgstr "Régularization des ecocups" -#: counter/models.py:90 +#: counter/models.py:91 msgid "account id" msgstr "numéro de compte" -#: counter/models.py:92 +#: counter/models.py:93 msgid "recorded product" msgstr "produits consignés" -#: counter/models.py:97 +#: counter/models.py:98 msgid "customer" msgstr "client" -#: counter/models.py:98 +#: counter/models.py:99 msgid "customers" msgstr "clients" -#: counter/models.py:110 counter/views/click.py:66 +#: counter/models.py:111 counter/views/click.py:68 msgid "Not enough money" msgstr "Solde insuffisant" -#: counter/models.py:196 +#: counter/models.py:197 msgid "First name" msgstr "Prénom" -#: counter/models.py:197 +#: counter/models.py:198 msgid "Last name" msgstr "Nom de famille" -#: counter/models.py:198 +#: counter/models.py:199 msgid "Address 1" msgstr "Adresse 1" -#: counter/models.py:199 +#: counter/models.py:200 msgid "Address 2" msgstr "Adresse 2" -#: counter/models.py:200 +#: counter/models.py:201 msgid "Zip code" msgstr "Code postal" -#: counter/models.py:201 +#: counter/models.py:202 msgid "City" msgstr "Ville" -#: counter/models.py:202 +#: counter/models.py:203 msgid "Country" msgstr "Pays" -#: counter/models.py:210 +#: counter/models.py:211 msgid "Phone number" msgstr "Numéro de téléphone" -#: counter/models.py:252 +#: counter/models.py:253 msgid "When the mail warning that the account was about to be dumped was sent." msgstr "Quand le mail d'avertissement de la vidange du compte a été envoyé." -#: counter/models.py:257 +#: counter/models.py:258 msgid "Set this to True if the warning mail received an error" msgstr "Mettre à True si le mail a reçu une erreur" -#: counter/models.py:264 +#: counter/models.py:265 msgid "The operation that emptied the account." msgstr "L'opération qui a vidé le compte." -#: counter/models.py:309 counter/models.py:333 +#: counter/models.py:310 counter/models.py:334 msgid "product type" msgstr "type du produit" -#: counter/models.py:340 +#: counter/models.py:341 msgid "purchase price" msgstr "prix d'achat" -#: counter/models.py:341 +#: counter/models.py:342 msgid "Initial cost of purchasing the product" msgstr "Coût initial d'achat du produit" -#: counter/models.py:343 +#: counter/models.py:344 msgid "selling price" msgstr "prix de vente" -#: counter/models.py:345 +#: counter/models.py:346 msgid "special selling price" msgstr "prix de vente spécial" -#: counter/models.py:346 +#: counter/models.py:347 msgid "Price for barmen during their permanence" msgstr "Prix pour les barmen durant leur permanence" -#: counter/models.py:354 +#: counter/models.py:355 msgid "icon" msgstr "icône" -#: counter/models.py:359 +#: counter/models.py:360 msgid "limit age" msgstr "âge limite" -#: counter/models.py:360 +#: counter/models.py:361 msgid "tray price" msgstr "prix plateau" -#: counter/models.py:362 +#: counter/models.py:363 msgid "buying groups" msgstr "groupe d'achat" -#: counter/models.py:364 election/models.py:50 +#: counter/models.py:365 election/models.py:50 msgid "archived" msgstr "archivé" -#: counter/models.py:367 counter/models.py:1090 +#: counter/models.py:368 counter/models.py:1100 msgid "product" msgstr "produit" -#: counter/models.py:484 +#: counter/models.py:485 msgid "products" msgstr "produits" -#: counter/models.py:487 +#: counter/models.py:488 msgid "counter type" msgstr "type de comptoir" -#: counter/models.py:489 +#: counter/models.py:490 msgid "Bar" msgstr "Bar" -#: counter/models.py:489 +#: counter/models.py:490 msgid "Office" msgstr "Bureau" -#: counter/models.py:492 +#: counter/models.py:493 msgid "sellers" msgstr "vendeurs" -#: counter/models.py:500 launderette/models.py:178 +#: counter/models.py:501 launderette/models.py:178 msgid "token" msgstr "jeton" -#: counter/models.py:695 +#: counter/models.py:705 msgid "bank" msgstr "banque" -#: counter/models.py:697 counter/models.py:798 +#: counter/models.py:707 counter/models.py:808 msgid "is validated" msgstr "est validé" -#: counter/models.py:702 +#: counter/models.py:712 msgid "refilling" msgstr "rechargement" -#: counter/models.py:775 eboutic/models.py:249 +#: counter/models.py:785 eboutic/models.py:249 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:776 counter/models.py:1070 eboutic/models.py:250 +#: counter/models.py:786 counter/models.py:1080 eboutic/models.py:250 msgid "quantity" msgstr "quantité" -#: counter/models.py:795 +#: counter/models.py:805 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:795 sith/settings.py:415 sith/settings.py:420 -#: sith/settings.py:440 -msgid "Credit card" -msgstr "Carte bancaire" - -#: counter/models.py:803 +#: counter/models.py:813 msgid "selling" msgstr "vente" -#: counter/models.py:907 +#: counter/models.py:917 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:908 +#: counter/models.py:918 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:910 counter/models.py:923 +#: counter/models.py:920 counter/models.py:933 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3809,67 +3822,67 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:961 +#: counter/models.py:971 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:964 +#: counter/models.py:974 msgid "permanency" msgstr "permanence" -#: counter/models.py:997 +#: counter/models.py:1007 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:1000 +#: counter/models.py:1010 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:1066 +#: counter/models.py:1076 msgid "cash summary" msgstr "relevé" -#: counter/models.py:1069 +#: counter/models.py:1079 msgid "value" msgstr "valeur" -#: counter/models.py:1072 +#: counter/models.py:1082 msgid "check" msgstr "chèque" -#: counter/models.py:1074 +#: counter/models.py:1084 msgid "True if this is a bank check, else False" msgstr "Vrai si c'est un chèque, sinon Faux." -#: counter/models.py:1078 +#: counter/models.py:1088 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1094 +#: counter/models.py:1104 msgid "banner" msgstr "bannière" -#: counter/models.py:1096 +#: counter/models.py:1106 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1098 +#: counter/models.py:1108 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1100 +#: counter/models.py:1110 msgid "secret" msgstr "secret" -#: counter/models.py:1139 +#: counter/models.py:1149 msgid "uid" msgstr "uid" -#: counter/models.py:1144 counter/models.py:1149 +#: counter/models.py:1154 counter/models.py:1159 msgid "student card" msgstr "carte étudiante" -#: counter/models.py:1150 +#: counter/models.py:1160 msgid "student cards" msgstr "cartes étudiantes" @@ -3929,14 +3942,14 @@ msgstr "oui" msgid "There is no cash register summary in this website." msgstr "Il n'y a pas de relevé de caisse dans ce site web." -#: counter/templates/counter/counter_click.jinja:39 +#: counter/templates/counter/counter_click.jinja:35 #: launderette/templates/launderette/launderette_admin.jinja:8 msgid "Selling" msgstr "Vente" -#: counter/templates/counter/counter_click.jinja:50 -#: counter/templates/counter/counter_click.jinja:115 -#: counter/templates/counter/fragments/create_student_card.jinja:11 +#: counter/templates/counter/counter_click.jinja:46 +#: counter/templates/counter/fragments/create_refill.jinja:8 +#: counter/templates/counter/fragments/create_student_card.jinja:10 #: counter/templates/counter/invoices_call.jinja:16 #: launderette/templates/launderette/launderette_admin.jinja:35 #: launderette/templates/launderette/launderette_click.jinja:13 @@ -3945,16 +3958,16 @@ msgstr "Vente" msgid "Go" msgstr "Valider" -#: counter/templates/counter/counter_click.jinja:57 +#: counter/templates/counter/counter_click.jinja:53 #: eboutic/templates/eboutic/eboutic_makecommand.jinja:19 msgid "Basket: " msgstr "Panier : " -#: counter/templates/counter/counter_click.jinja:98 +#: counter/templates/counter/counter_click.jinja:94 msgid "Finish" msgstr "Terminer" -#: counter/templates/counter/counter_click.jinja:108 +#: counter/templates/counter/counter_click.jinja:104 #: counter/templates/counter/refilling_list.jinja:9 msgid "Refilling" msgstr "Rechargement" @@ -4038,23 +4051,19 @@ msgstr "Nouveau eticket" msgid "There is no eticket in this website." msgstr "Il n'y a pas de eticket sur ce site web." -#: counter/templates/counter/fragments/create_student_card.jinja:2 -msgid "Student card" -msgstr "Carte étudiante" - -#: counter/templates/counter/fragments/create_student_card.jinja:13 +#: counter/templates/counter/fragments/create_student_card.jinja:12 msgid "No student card registered." msgstr "Aucune carte étudiante enregistrée." +#: counter/templates/counter/fragments/create_student_card.jinja:15 +#, python-format +msgid "uid: %(uid)s " +msgstr "uid: %(uid)s" + #: counter/templates/counter/fragments/create_student_card.jinja:16 msgid "Card registered" msgstr "Carte enregistrée" -#: counter/templates/counter/fragments/create_student_card.jinja:17 -#, python-format -msgid "uid: %(uid)s " -msgstr "uid: %(uid)s" - #: counter/templates/counter/invoices_call.jinja:8 #, python-format msgid "Invoices call for %(date)s" @@ -4277,19 +4286,19 @@ msgstr "Montant du chèque" msgid "Check quantity" msgstr "Nombre de chèque" -#: counter/views/click.py:57 +#: counter/views/click.py:59 msgid "Too young for that product" msgstr "Trop jeune pour ce produit" -#: counter/views/click.py:60 +#: counter/views/click.py:62 msgid "Not allowed for that product" msgstr "Non autorisé pour ce produit" -#: counter/views/click.py:63 +#: counter/views/click.py:65 msgid "No date of birth provided" msgstr "Pas de date de naissance renseignée" -#: counter/views/click.py:331 +#: counter/views/click.py:325 msgid "You have not enough money to buy all the basket" msgstr "Vous n'avez pas assez d'argent pour acheter le panier" @@ -4907,12 +4916,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:658 +#: sith/settings.py:652 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:658 +#: sith/settings.py:652 msgid "Drying" msgstr "Séchage" @@ -5427,11 +5436,11 @@ msgstr "Personne(s)" msgid "Identify users on pictures" msgstr "Identifiez les utilisateurs sur les photos" -#: sith/settings.py:253 sith/settings.py:477 +#: sith/settings.py:253 sith/settings.py:471 msgid "English" msgstr "Anglais" -#: sith/settings.py:253 sith/settings.py:476 +#: sith/settings.py:253 sith/settings.py:470 msgid "French" msgstr "Français" @@ -5455,7 +5464,7 @@ msgstr "INFO" msgid "GI" msgstr "GI" -#: sith/settings.py:401 sith/settings.py:487 +#: sith/settings.py:401 sith/settings.py:481 msgid "E" msgstr "E" @@ -5487,14 +5496,6 @@ msgstr "Humanités" msgid "N/A" msgstr "N/A" -#: sith/settings.py:412 sith/settings.py:419 sith/settings.py:438 -msgid "Check" -msgstr "Chèque" - -#: sith/settings.py:413 sith/settings.py:421 sith/settings.py:439 -msgid "Cash" -msgstr "Espèces" - #: sith/settings.py:414 msgid "Transfert" msgstr "Virement" @@ -5511,296 +5512,296 @@ msgstr "Sevenans" msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:457 +#: sith/settings.py:451 msgid "Free" msgstr "Libre" -#: sith/settings.py:458 +#: sith/settings.py:452 msgid "CS" msgstr "CS" -#: sith/settings.py:459 +#: sith/settings.py:453 msgid "TM" msgstr "TM" -#: sith/settings.py:460 +#: sith/settings.py:454 msgid "OM" msgstr "OM" -#: sith/settings.py:461 +#: sith/settings.py:455 msgid "QC" msgstr "QC" -#: sith/settings.py:462 +#: sith/settings.py:456 msgid "EC" msgstr "EC" -#: sith/settings.py:463 +#: sith/settings.py:457 msgid "RN" msgstr "RN" -#: sith/settings.py:464 +#: sith/settings.py:458 msgid "ST" msgstr "ST" -#: sith/settings.py:465 +#: sith/settings.py:459 msgid "EXT" msgstr "EXT" -#: sith/settings.py:470 +#: sith/settings.py:464 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:471 +#: sith/settings.py:465 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:472 +#: sith/settings.py:466 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:478 +#: sith/settings.py:472 msgid "German" msgstr "Allemand" -#: sith/settings.py:479 +#: sith/settings.py:473 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:483 +#: sith/settings.py:477 msgid "A" msgstr "A" -#: sith/settings.py:484 +#: sith/settings.py:478 msgid "B" msgstr "B" -#: sith/settings.py:485 +#: sith/settings.py:479 msgid "C" msgstr "C" -#: sith/settings.py:486 +#: sith/settings.py:480 msgid "D" msgstr "D" -#: sith/settings.py:488 +#: sith/settings.py:482 msgid "FX" msgstr "FX" -#: sith/settings.py:489 +#: sith/settings.py:483 msgid "F" msgstr "F" -#: sith/settings.py:490 +#: sith/settings.py:484 msgid "Abs" msgstr "Abs" -#: sith/settings.py:494 +#: sith/settings.py:488 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:495 +#: sith/settings.py:489 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:539 +#: sith/settings.py:533 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:540 +#: sith/settings.py:534 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:542 +#: sith/settings.py:536 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:546 +#: sith/settings.py:540 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:547 +#: sith/settings.py:541 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:548 +#: sith/settings.py:542 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:549 +#: sith/settings.py:543 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:550 +#: sith/settings.py:544 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:551 +#: sith/settings.py:545 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:552 +#: sith/settings.py:546 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:553 +#: sith/settings.py:547 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:555 +#: sith/settings.py:549 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:559 +#: sith/settings.py:553 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:560 +#: sith/settings.py:554 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:561 +#: sith/settings.py:555 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:563 +#: sith/settings.py:557 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:567 +#: sith/settings.py:561 msgid "One day" msgstr "Un jour" -#: sith/settings.py:568 +#: sith/settings.py:562 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:571 +#: sith/settings.py:565 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:576 +#: sith/settings.py:570 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:581 +#: sith/settings.py:575 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:586 +#: sith/settings.py:580 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:591 +#: sith/settings.py:585 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:597 +#: sith/settings.py:591 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:617 +#: sith/settings.py:611 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:618 +#: sith/settings.py:612 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:619 +#: sith/settings.py:613 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:620 +#: sith/settings.py:614 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:621 +#: sith/settings.py:615 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:622 +#: sith/settings.py:616 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:623 +#: sith/settings.py:617 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:624 +#: sith/settings.py:618 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:625 +#: sith/settings.py:619 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:662 +#: sith/settings.py:656 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:663 +#: sith/settings.py:657 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:666 +#: sith/settings.py:660 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:668 +#: sith/settings.py:662 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:669 +#: sith/settings.py:663 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:670 +#: sith/settings.py:664 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:671 +#: sith/settings.py:665 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:672 +#: sith/settings.py:666 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:673 +#: sith/settings.py:667 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:674 +#: sith/settings.py:668 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:686 +#: sith/settings.py:680 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:687 +#: sith/settings.py:681 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:688 +#: sith/settings.py:682 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:689 +#: sith/settings.py:683 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:690 +#: sith/settings.py:684 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:698 +#: sith/settings.py:692 msgid "AE tee-shirt" msgstr "Tee-shirt AE" diff --git a/sith/settings.py b/sith/settings.py index ba0a35da..56c6c0bc 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -434,12 +434,6 @@ SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"} -SITH_COUNTER_PAYMENT_METHOD = [ - ("CHECK", _("Check")), - ("CASH", _("Cash")), - ("CARD", _("Credit card")), -] - SITH_COUNTER_BANK = [ ("OTHER", "Autre"), ("SOCIETE-GENERALE", "Société générale"), diff --git a/subscription/views.py b/subscription/views.py index 2948391b..b285a137 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -21,6 +21,7 @@ from django.utils.timezone import localdate from django.views.generic import CreateView, DetailView, TemplateView from django.views.generic.edit import FormView +from counter.apps import PAYMENT_METHOD from subscription.forms import ( SelectionDateForm, SubscriptionExistingUserForm, @@ -108,6 +109,6 @@ class SubscriptionsStatsView(FormView): subscription_end__gte=self.end_date, subscription_start__lte=self.start_date ) kwargs["subscriptions_types"] = settings.SITH_SUBSCRIPTIONS - kwargs["payment_types"] = settings.SITH_COUNTER_PAYMENT_METHOD + kwargs["payment_types"] = PAYMENT_METHOD kwargs["locations"] = settings.SITH_SUBSCRIPTION_LOCATIONS return kwargs From 379527cd58dc694d5396a02dee7f8130fa8934ff Mon Sep 17 00:00:00 2001 From: Sli Date: Mon, 16 Dec 2024 00:58:23 +0100 Subject: [PATCH 4/9] Add a nice animation on successful refilling --- counter/static/bundled/counter/counter-click-index.ts | 5 +++++ counter/templates/counter/counter_click.jinja | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/counter/static/bundled/counter/counter-click-index.ts b/counter/static/bundled/counter/counter-click-index.ts index 01d7e8f9..044d14de 100644 --- a/counter/static/bundled/counter/counter-click-index.ts +++ b/counter/static/bundled/counter/counter-click-index.ts @@ -44,6 +44,11 @@ exportToHtml("loadCounter", (config: CounterConfig) => { ).data.amount; }, + async onRefillingSuccess() { + await this.updateBalance(); + document.getElementById("selling-accordion").click(); + }, + async handleCode(event: SubmitEvent) { const code = ( $(event.target).find("#code_field").val() as string diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index bc5c4e58..f9821bf1 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -32,7 +32,7 @@
-
{% trans %}Selling{% endtrans %}
+
{% trans %}Selling{% endtrans %}
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %} @@ -103,7 +103,7 @@ {% if refilling_fragment %}
{% trans %}Refilling{% endtrans %}
{{ refilling_fragment }}
From 66e5ef64fd5c5dab3503fb7bd6edae6a834af5a7 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 17 Dec 2024 00:45:51 +0100 Subject: [PATCH 5/9] Don't use API to update amount after a refilling query --- .../bundled/counter/counter-click-index.ts | 21 +++++++------------ counter/templates/counter/counter_click.jinja | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/counter/static/bundled/counter/counter-click-index.ts b/counter/static/bundled/counter/counter-click-index.ts index 044d14de..b8de64be 100644 --- a/counter/static/bundled/counter/counter-click-index.ts +++ b/counter/static/bundled/counter/counter-click-index.ts @@ -1,5 +1,4 @@ import { exportToHtml } from "#core:utils/globals"; -import { customerGetCustomer } from "#openapi"; interface CounterConfig { csrfToken: string; @@ -33,19 +32,13 @@ exportToHtml("loadCounter", (config: CounterConfig) => { return total / 100; }, - async updateBalance() { - this.customerBalance = ( - await customerGetCustomer({ - path: { - // biome-ignore lint/style/useNamingConvention: api is in snake_case - customer_id: config.customerId, - }, - }) - ).data.amount; - }, - - async onRefillingSuccess() { - await this.updateBalance(); + onRefillingSuccess(event: CustomEvent) { + if (event.type !== "htmx:after-request" || event.detail.failed) { + return; + } + this.customerBalance += Number.parseFloat( + (event.detail.target.querySelector("#id_amount") as HTMLInputElement).value, + ); document.getElementById("selling-accordion").click(); }, diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index f9821bf1..cb62be31 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -103,7 +103,7 @@ {% if refilling_fragment %}
{% trans %}Refilling{% endtrans %}
{{ refilling_fragment }}
From a0eb53a607d8e8baed40d3b5d480d2943b9fe183 Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 17 Dec 2024 01:41:45 +0100 Subject: [PATCH 6/9] Apply review comments --- counter/models.py | 11 +++++------ counter/views/click.py | 2 +- counter/views/student_card.py | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/counter/models.py b/counter/models.py index 3640c3f5..18215856 100644 --- a/counter/models.py +++ b/counter/models.py @@ -652,13 +652,12 @@ class Counter(models.Model): )["total"] def customer_is_barman(self, customer: Customer | User) -> bool: - """Check if current counter is a `bar` and that the customer is on the barmen_list + """Check if this counter is a `bar` and if the customer is currently logged in. + This is useful to compute special prices.""" - This is useful to compute special prices""" - if isinstance(customer, Customer): - customer: User = customer.user - - return self.type == "BAR" and customer in self.barmen_list + # Customer and User are two different tables, + # but they share the same primary key + return self.type == "BAR" and any(b.pk == customer.pk for b in self.barmen_list) class RefillingQuerySet(models.QuerySet): diff --git a/counter/views/click.py b/counter/views/click.py index c6bafa57..9542b467 100644 --- a/counter/views/click.py +++ b/counter/views/click.py @@ -417,7 +417,7 @@ class RefillingCreateView(FormView): def get_template_data( cls, customer: Customer, *, form_instance: form_class | None = None ) -> FormFragmentTemplateData[form_class]: - return FormFragmentTemplateData[cls.form_class]( + return FormFragmentTemplateData( form=form_instance if form_instance else cls.form_class(), template=cls.template_name, context={ diff --git a/counter/views/student_card.py b/counter/views/student_card.py index 952f65eb..f916260b 100644 --- a/counter/views/student_card.py +++ b/counter/views/student_card.py @@ -73,7 +73,7 @@ class StudentCardFormView(FormView): cls, customer: Customer, *, form_instance: form_class | None = None ) -> FormFragmentTemplateData[form_class]: """Get necessary data to pre-render the fragment""" - return FormFragmentTemplateData[cls.form_class]( + return FormFragmentTemplateData( form=form_instance if form_instance else cls.form_class(), template=cls.template_name, context={ From fc0ef29738086e843fda5fb3a40139377a7a8c7b Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 17 Dec 2024 01:42:10 +0100 Subject: [PATCH 7/9] Remove GetCustomer API endpoint --- counter/api.py | 17 +----- counter/schemas.py | 8 +-- counter/tests/test_api.py | 105 ---------------------------------- counter/tests/test_counter.py | 4 +- 4 files changed, 4 insertions(+), 130 deletions(-) delete mode 100644 counter/tests/test_api.py diff --git a/counter/api.py b/counter/api.py index e51aea26..f3f0f101 100644 --- a/counter/api.py +++ b/counter/api.py @@ -21,12 +21,11 @@ from ninja_extra import ControllerBase, api_controller, paginate, route from ninja_extra.pagination import PageNumberPaginationExtra from ninja_extra.schemas import PaginatedResponseSchema -from core.api_permissions import CanAccessLookup, CanView, IsLoggedInCounter, IsRoot -from counter.models import Counter, Customer, Product +from core.api_permissions import CanAccessLookup, CanView, IsRoot +from counter.models import Counter, Product from counter.schemas import ( CounterFilterSchema, CounterSchema, - CustomerSchema, ProductSchema, SimplifiedCounterSchema, ) @@ -61,18 +60,6 @@ class CounterController(ControllerBase): return filters.filter(Counter.objects.all()) -@api_controller("/customer") -class CustomerController(ControllerBase): - @route.get( - "{customer_id}", - response=CustomerSchema, - permissions=[IsLoggedInCounter], - url_name="get_customer", - ) - def get_customer(self, customer_id: int): - return self.get_object_or_exception(Customer, pk=customer_id) - - @api_controller("/product") class ProductController(ControllerBase): @route.get( diff --git a/counter/schemas.py b/counter/schemas.py index 7fbe1a71..ec1a842d 100644 --- a/counter/schemas.py +++ b/counter/schemas.py @@ -4,7 +4,7 @@ from annotated_types import MinLen from ninja import Field, FilterSchema, ModelSchema from core.schemas import SimpleUserSchema -from counter.models import Counter, Customer, Product +from counter.models import Counter, Product class CounterSchema(ModelSchema): @@ -16,12 +16,6 @@ class CounterSchema(ModelSchema): fields = ["id", "name", "type", "club", "products"] -class CustomerSchema(ModelSchema): - class Meta: - model = Customer - fields = ["user", "account_id", "amount", "recorded_products"] - - class CounterFilterSchema(FilterSchema): search: Annotated[str, MinLen(1)] = Field(None, q="name__icontains") diff --git a/counter/tests/test_api.py b/counter/tests/test_api.py deleted file mode 100644 index 8a5efdc1..00000000 --- a/counter/tests/test_api.py +++ /dev/null @@ -1,105 +0,0 @@ -import pytest -from django.contrib.auth.models import make_password -from django.test.client import Client -from django.urls import reverse -from model_bakery import baker - -from core.baker_recipes import board_user, subscriber_user -from core.models import User -from counter.models import Counter - - -@pytest.fixture -def customer_user() -> User: - return subscriber_user.make() - - -@pytest.fixture -def counter_bar() -> Counter: - return baker.make(Counter, type="BAR") - - -@pytest.fixture -def barmen(counter_bar: Counter) -> User: - user = subscriber_user.make(password=make_password("plop")) - counter_bar.sellers.add(user) - return user - - -@pytest.fixture -def board_member() -> User: - return board_user.make() - - -@pytest.fixture -def root_user() -> User: - return baker.make(User, is_superuser=True) - - -@pytest.mark.django_db -@pytest.mark.parametrize( - ("connected_user"), - [ - None, # Anonymous user - "barmen", - "customer_user", - "board_member", - "root_user", - ], -) -def test_get_customer_fail( - client: Client, - customer_user: User, - request: pytest.FixtureRequest, - connected_user: str | None, -): - if connected_user is not None: - client.force_login(request.getfixturevalue(connected_user)) - assert ( - client.get( - reverse("api:get_customer", kwargs={"customer_id": customer_user.id}) - ).status_code - == 403 - ) - - -@pytest.mark.django_db -def test_get_customer_from_bar_fail_wrong_referrer( - client: Client, customer_user: User, barmen: User, counter_bar: Counter -): - client.post( - reverse("counter:login", args=[counter_bar.pk]), - {"username": barmen.username, "password": "plop"}, - ) - - assert ( - client.get( - reverse("api:get_customer", kwargs={"customer_id": customer_user.id}) - ).status_code - == 403 - ) - - -@pytest.mark.django_db -def test_get_customer_from_bar_success( - client: Client, customer_user: User, barmen: User, counter_bar: Counter -): - client.post( - reverse("counter:login", args=[counter_bar.pk]), - {"username": barmen.username, "password": "plop"}, - ) - - response = client.get( - reverse("api:get_customer", kwargs={"customer_id": customer_user.id}), - HTTP_REFERER=reverse( - "counter:click", - kwargs={"counter_id": counter_bar.id, "user_id": customer_user.id}, - ), - ) - assert response.status_code == 200 - assert response.json() == { - "user": customer_user.id, - "account_id": customer_user.customer.account_id, - "amount": f"{customer_user.customer.amount:.2f}", - "recorded_products": customer_user.customer.recorded_products, - } diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index 2574865f..9c6d3b7a 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -72,9 +72,7 @@ class TestCounter(TestCase): kwargs={"customer_id": self.richard.customer.pk}, ) - response = self.client.get( - response.get("location"), - ) + response = self.client.get(counter_url) assert ">Richard Batsbak Date: Tue, 17 Dec 2024 02:42:07 +0100 Subject: [PATCH 8/9] Fix refill permissions * Remove ability to refill from counters * Fix bug where you could refill without any board member on a BAR * Add a warning message explaining why refilling are disabled --- counter/models.py | 5 +- counter/templates/counter/counter_click.jinja | 35 ++- counter/tests/test_counter.py | 2 +- locale/fr/LC_MESSAGES/django.po | 246 +++++++++--------- sith/settings.py | 2 - 5 files changed, 152 insertions(+), 138 deletions(-) diff --git a/counter/models.py b/counter/models.py index 18215856..087baffc 100644 --- a/counter/models.py +++ b/counter/models.py @@ -43,7 +43,7 @@ from core.fields import ResizedImageField from core.models import Group, Notification, User from core.utils import get_start_of_semester from counter.apps import PAYMENT_METHOD -from sith.settings import SITH_COUNTER_OFFICES, SITH_MAIN_CLUB +from sith.settings import SITH_MAIN_CLUB from subscription.models import Subscription @@ -559,9 +559,6 @@ class Counter(models.Model): """Show if the counter authorize the refilling with physic money.""" if self.type != "BAR": return False - if self.id in SITH_COUNTER_OFFICES: - # If the counter is either 'AE' or 'BdF', refills are authorized - return True # at least one of the barmen is in the AE board ae = Club.objects.get(unix_name=SITH_MAIN_CLUB["unix_name"]) return any(ae.get_membership_for(barman) for barman in self.barmen_list) diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index cb62be31..96a72435 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -31,7 +31,7 @@

{% trans %}Amount: {% endtrans %}

-
+
{% trans %}Selling{% endtrans %}
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %} @@ -100,19 +100,28 @@
- {% if refilling_fragment %} + {% if object.type == "BAR" %}
{% trans %}Refilling{% endtrans %}
-
- {{ refilling_fragment }} -
- {% endif %} - {% if student_card_fragment %} -
{% trans %}Student card{% endtrans %}
-
- {{ student_card_fragment }} -
+ {% if refilling_fragment %} +
+ {{ refilling_fragment }} +
+ {% else %} +
+

+ {% trans trimmed %}As a barman, you are not able to refill any account on your own. An admin should be connected on this counter for that. The customer can refill by using the eboutic.{% endtrans %} +

+
+ {% endif %} + {% if student_card_fragment %} +
{% trans %}Student card{% endtrans %}
+
+ {{ student_card_fragment }} +
+ {% endif %} + {% endif %}
diff --git a/counter/tests/test_counter.py b/counter/tests/test_counter.py index 9c6d3b7a..154578c6 100644 --- a/counter/tests/test_counter.py +++ b/counter/tests/test_counter.py @@ -159,7 +159,7 @@ class TestCounter(TestCase): }, HTTP_REFERER=counter_url, ) - assert response.status_code == 302 + assert response.status_code == 403 # Krophil is not board admin def test_annotate_has_barman_queryset(self): """Test if the custom queryset method `annotate_has_barman` works as intended.""" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index dd9aef28..6fb47e5b 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-16 00:11+0100\n" +"POT-Creation-Date: 2024-12-17 02:39+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -87,12 +87,12 @@ msgstr "Compte club" msgid "%(club_account)s on %(bank_account)s" msgstr "%(club_account)s sur %(bank_account)s" -#: accounting/models.py:188 club/models.py:351 counter/models.py:969 +#: accounting/models.py:188 club/models.py:351 counter/models.py:965 #: election/models.py:16 launderette/models.py:165 msgid "start date" msgstr "date de début" -#: accounting/models.py:189 club/models.py:352 counter/models.py:970 +#: accounting/models.py:189 club/models.py:352 counter/models.py:966 #: election/models.py:17 msgid "end date" msgstr "date de fin" @@ -106,7 +106,7 @@ msgid "club account" msgstr "compte club" #: accounting/models.py:199 accounting/models.py:255 counter/models.py:92 -#: counter/models.py:687 +#: counter/models.py:683 msgid "amount" msgstr "montant" @@ -128,18 +128,18 @@ msgstr "classeur" #: accounting/models.py:256 core/models.py:956 core/models.py:1467 #: core/models.py:1512 core/models.py:1541 core/models.py:1565 -#: counter/models.py:697 counter/models.py:801 counter/models.py:1005 +#: counter/models.py:693 counter/models.py:797 counter/models.py:1001 #: eboutic/models.py:57 eboutic/models.py:193 forum/models.py:312 #: forum/models.py:413 msgid "date" msgstr "date" -#: accounting/models.py:257 counter/models.py:300 counter/models.py:1006 +#: accounting/models.py:257 counter/models.py:300 counter/models.py:1002 #: pedagogy/models.py:208 msgid "comment" msgstr "commentaire" -#: accounting/models.py:259 counter/models.py:699 counter/models.py:803 +#: accounting/models.py:259 counter/models.py:695 counter/models.py:799 #: subscription/models.py:56 msgid "payment method" msgstr "méthode de paiement" @@ -166,7 +166,7 @@ msgstr "type comptable" #: accounting/models.py:294 accounting/models.py:429 accounting/models.py:460 #: accounting/models.py:492 core/models.py:1540 core/models.py:1566 -#: counter/models.py:767 +#: counter/models.py:763 msgid "label" msgstr "étiquette" @@ -517,7 +517,7 @@ msgid "Effective amount" msgstr "Montant effectif" #: accounting/templates/accounting/club_account_details.jinja:36 -#: sith/settings.py:463 +#: sith/settings.py:461 msgid "Closed" msgstr "Fermé" @@ -1041,7 +1041,7 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs" msgid "A club with that unix_name already exists" msgstr "Un club avec ce nom UNIX existe déjà." -#: club/models.py:337 counter/models.py:960 counter/models.py:996 +#: club/models.py:337 counter/models.py:956 counter/models.py:992 #: eboutic/models.py:53 eboutic/models.py:189 election/models.py:183 #: launderette/models.py:130 launderette/models.py:184 sas/models.py:273 #: trombi/models.py:205 @@ -3301,7 +3301,7 @@ msgid "Go to my Trombi tools" msgstr "Allez à mes outils de Trombi" #: core/templates/core/user_preferences.jinja:39 -#: counter/templates/counter/counter_click.jinja:112 +#: counter/templates/counter/counter_click.jinja:119 msgid "Student card" msgstr "Carte étudiante" @@ -3607,13 +3607,13 @@ msgstr "Chèque" msgid "Cash" msgstr "Espèces" -#: counter/apps.py:30 counter/models.py:805 sith/settings.py:415 +#: counter/apps.py:30 counter/models.py:801 sith/settings.py:415 #: sith/settings.py:420 msgid "Credit card" msgstr "Carte bancaire" -#: counter/apps.py:36 counter/models.py:506 counter/models.py:966 -#: counter/models.py:1002 launderette/models.py:32 +#: counter/apps.py:36 counter/models.py:506 counter/models.py:962 +#: counter/models.py:998 launderette/models.py:32 msgid "counter" msgstr "comptoir" @@ -3745,7 +3745,7 @@ msgstr "groupe d'achat" msgid "archived" msgstr "archivé" -#: counter/models.py:368 counter/models.py:1100 +#: counter/models.py:368 counter/models.py:1096 msgid "product" msgstr "produit" @@ -3773,44 +3773,44 @@ msgstr "vendeurs" msgid "token" msgstr "jeton" -#: counter/models.py:705 +#: counter/models.py:701 msgid "bank" msgstr "banque" -#: counter/models.py:707 counter/models.py:808 +#: counter/models.py:703 counter/models.py:804 msgid "is validated" msgstr "est validé" -#: counter/models.py:712 +#: counter/models.py:708 msgid "refilling" msgstr "rechargement" -#: counter/models.py:785 eboutic/models.py:249 +#: counter/models.py:781 eboutic/models.py:249 msgid "unit price" msgstr "prix unitaire" -#: counter/models.py:786 counter/models.py:1080 eboutic/models.py:250 +#: counter/models.py:782 counter/models.py:1076 eboutic/models.py:250 msgid "quantity" msgstr "quantité" -#: counter/models.py:805 +#: counter/models.py:801 msgid "Sith account" msgstr "Compte utilisateur" -#: counter/models.py:813 +#: counter/models.py:809 msgid "selling" msgstr "vente" -#: counter/models.py:917 +#: counter/models.py:913 msgid "Unknown event" msgstr "Événement inconnu" -#: counter/models.py:918 +#: counter/models.py:914 #, python-format msgid "Eticket bought for the event %(event)s" msgstr "Eticket acheté pour l'événement %(event)s" -#: counter/models.py:920 counter/models.py:933 +#: counter/models.py:916 counter/models.py:929 #, python-format msgid "" "You bought an eticket for the event %(event)s.\n" @@ -3822,67 +3822,67 @@ msgstr "" "Vous pouvez également retrouver tous vos e-tickets sur votre page de compte " "%(url)s." -#: counter/models.py:971 +#: counter/models.py:967 msgid "last activity date" msgstr "dernière activité" -#: counter/models.py:974 +#: counter/models.py:970 msgid "permanency" msgstr "permanence" -#: counter/models.py:1007 +#: counter/models.py:1003 msgid "emptied" msgstr "coffre vidée" -#: counter/models.py:1010 +#: counter/models.py:1006 msgid "cash register summary" msgstr "relevé de caisse" -#: counter/models.py:1076 +#: counter/models.py:1072 msgid "cash summary" msgstr "relevé" -#: counter/models.py:1079 +#: counter/models.py:1075 msgid "value" msgstr "valeur" -#: counter/models.py:1082 +#: counter/models.py:1078 msgid "check" msgstr "chèque" -#: counter/models.py:1084 +#: counter/models.py:1080 msgid "True if this is a bank check, else False" msgstr "Vrai si c'est un chèque, sinon Faux." -#: counter/models.py:1088 +#: counter/models.py:1084 msgid "cash register summary item" msgstr "élément de relevé de caisse" -#: counter/models.py:1104 +#: counter/models.py:1100 msgid "banner" msgstr "bannière" -#: counter/models.py:1106 +#: counter/models.py:1102 msgid "event date" msgstr "date de l'événement" -#: counter/models.py:1108 +#: counter/models.py:1104 msgid "event title" msgstr "titre de l'événement" -#: counter/models.py:1110 +#: counter/models.py:1106 msgid "secret" msgstr "secret" -#: counter/models.py:1149 +#: counter/models.py:1145 msgid "uid" msgstr "uid" -#: counter/models.py:1154 counter/models.py:1159 +#: counter/models.py:1150 counter/models.py:1155 msgid "student card" msgstr "carte étudiante" -#: counter/models.py:1160 +#: counter/models.py:1156 msgid "student cards" msgstr "cartes étudiantes" @@ -3972,6 +3972,16 @@ msgstr "Terminer" msgid "Refilling" msgstr "Rechargement" +#: counter/templates/counter/counter_click.jinja:114 +msgid "" +"As a barman, you are not able to refill any account on your own. An admin " +"should be connected on this counter for that. The customer can refill by " +"using the eboutic." +msgstr "" +"En tant que barman, vous n'êtes pas en mesure de recharger un compte par vous même. " +"Un admin doit être connecté sur ce comptoir pour cela. Le client peut recharger son compte " +"en utilisant l'eboutic" + #: counter/templates/counter/counter_list.jinja:4 #: counter/templates/counter/counter_list.jinja:10 msgid "Counter admin list" @@ -4916,12 +4926,12 @@ msgid "Washing and drying" msgstr "Lavage et séchage" #: launderette/templates/launderette/launderette_book.jinja:27 -#: sith/settings.py:652 +#: sith/settings.py:650 msgid "Washing" msgstr "Lavage" #: launderette/templates/launderette/launderette_book.jinja:31 -#: sith/settings.py:652 +#: sith/settings.py:650 msgid "Drying" msgstr "Séchage" @@ -5436,11 +5446,11 @@ msgstr "Personne(s)" msgid "Identify users on pictures" msgstr "Identifiez les utilisateurs sur les photos" -#: sith/settings.py:253 sith/settings.py:471 +#: sith/settings.py:253 sith/settings.py:469 msgid "English" msgstr "Anglais" -#: sith/settings.py:253 sith/settings.py:470 +#: sith/settings.py:253 sith/settings.py:468 msgid "French" msgstr "Français" @@ -5464,7 +5474,7 @@ msgstr "INFO" msgid "GI" msgstr "GI" -#: sith/settings.py:401 sith/settings.py:481 +#: sith/settings.py:401 sith/settings.py:479 msgid "E" msgstr "E" @@ -5512,296 +5522,296 @@ msgstr "Sevenans" msgid "Montbéliard" msgstr "Montbéliard" -#: sith/settings.py:451 +#: sith/settings.py:449 msgid "Free" msgstr "Libre" -#: sith/settings.py:452 +#: sith/settings.py:450 msgid "CS" msgstr "CS" -#: sith/settings.py:453 +#: sith/settings.py:451 msgid "TM" msgstr "TM" -#: sith/settings.py:454 +#: sith/settings.py:452 msgid "OM" msgstr "OM" -#: sith/settings.py:455 +#: sith/settings.py:453 msgid "QC" msgstr "QC" -#: sith/settings.py:456 +#: sith/settings.py:454 msgid "EC" msgstr "EC" -#: sith/settings.py:457 +#: sith/settings.py:455 msgid "RN" msgstr "RN" -#: sith/settings.py:458 +#: sith/settings.py:456 msgid "ST" msgstr "ST" -#: sith/settings.py:459 +#: sith/settings.py:457 msgid "EXT" msgstr "EXT" -#: sith/settings.py:464 +#: sith/settings.py:462 msgid "Autumn" msgstr "Automne" -#: sith/settings.py:465 +#: sith/settings.py:463 msgid "Spring" msgstr "Printemps" -#: sith/settings.py:466 +#: sith/settings.py:464 msgid "Autumn and spring" msgstr "Automne et printemps" -#: sith/settings.py:472 +#: sith/settings.py:470 msgid "German" msgstr "Allemand" -#: sith/settings.py:473 +#: sith/settings.py:471 msgid "Spanish" msgstr "Espagnol" -#: sith/settings.py:477 +#: sith/settings.py:475 msgid "A" msgstr "A" -#: sith/settings.py:478 +#: sith/settings.py:476 msgid "B" msgstr "B" -#: sith/settings.py:479 +#: sith/settings.py:477 msgid "C" msgstr "C" -#: sith/settings.py:480 +#: sith/settings.py:478 msgid "D" msgstr "D" -#: sith/settings.py:482 +#: sith/settings.py:480 msgid "FX" msgstr "FX" -#: sith/settings.py:483 +#: sith/settings.py:481 msgid "F" msgstr "F" -#: sith/settings.py:484 +#: sith/settings.py:482 msgid "Abs" msgstr "Abs" -#: sith/settings.py:488 +#: sith/settings.py:486 msgid "Selling deletion" msgstr "Suppression de vente" -#: sith/settings.py:489 +#: sith/settings.py:487 msgid "Refilling deletion" msgstr "Suppression de rechargement" -#: sith/settings.py:533 +#: sith/settings.py:531 msgid "One semester" msgstr "Un semestre, 20 €" -#: sith/settings.py:534 +#: sith/settings.py:532 msgid "Two semesters" msgstr "Deux semestres, 35 €" -#: sith/settings.py:536 +#: sith/settings.py:534 msgid "Common core cursus" msgstr "Cursus tronc commun, 60 €" -#: sith/settings.py:540 +#: sith/settings.py:538 msgid "Branch cursus" msgstr "Cursus branche, 60 €" -#: sith/settings.py:541 +#: sith/settings.py:539 msgid "Alternating cursus" msgstr "Cursus alternant, 30 €" -#: sith/settings.py:542 +#: sith/settings.py:540 msgid "Honorary member" msgstr "Membre honoraire, 0 €" -#: sith/settings.py:543 +#: sith/settings.py:541 msgid "Assidu member" msgstr "Membre d'Assidu, 0 €" -#: sith/settings.py:544 +#: sith/settings.py:542 msgid "Amicale/DOCEO member" msgstr "Membre de l'Amicale/DOCEO, 0 €" -#: sith/settings.py:545 +#: sith/settings.py:543 msgid "UT network member" msgstr "Cotisant du réseau UT, 0 €" -#: sith/settings.py:546 +#: sith/settings.py:544 msgid "CROUS member" msgstr "Membres du CROUS, 0 €" -#: sith/settings.py:547 +#: sith/settings.py:545 msgid "Sbarro/ESTA member" msgstr "Membre de Sbarro ou de l'ESTA, 20 €" -#: sith/settings.py:549 +#: sith/settings.py:547 msgid "One semester Welcome Week" msgstr "Un semestre Welcome Week" -#: sith/settings.py:553 +#: sith/settings.py:551 msgid "One month for free" msgstr "Un mois gratuit" -#: sith/settings.py:554 +#: sith/settings.py:552 msgid "Two months for free" msgstr "Deux mois gratuits" -#: sith/settings.py:555 +#: sith/settings.py:553 msgid "Eurok's volunteer" msgstr "Bénévole Eurockéennes" -#: sith/settings.py:557 +#: sith/settings.py:555 msgid "Six weeks for free" msgstr "6 semaines gratuites" -#: sith/settings.py:561 +#: sith/settings.py:559 msgid "One day" msgstr "Un jour" -#: sith/settings.py:562 +#: sith/settings.py:560 msgid "GA staff member" msgstr "Membre staff GA (2 semaines), 1 €" -#: sith/settings.py:565 +#: sith/settings.py:563 msgid "One semester (-20%)" msgstr "Un semestre (-20%), 12 €" -#: sith/settings.py:570 +#: sith/settings.py:568 msgid "Two semesters (-20%)" msgstr "Deux semestres (-20%), 22 €" -#: sith/settings.py:575 +#: sith/settings.py:573 msgid "Common core cursus (-20%)" msgstr "Cursus tronc commun (-20%), 36 €" -#: sith/settings.py:580 +#: sith/settings.py:578 msgid "Branch cursus (-20%)" msgstr "Cursus branche (-20%), 36 €" -#: sith/settings.py:585 +#: sith/settings.py:583 msgid "Alternating cursus (-20%)" msgstr "Cursus alternant (-20%), 24 €" -#: sith/settings.py:591 +#: sith/settings.py:589 msgid "One year for free(CA offer)" msgstr "Une année offerte (Offre CA)" -#: sith/settings.py:611 +#: sith/settings.py:609 msgid "President" msgstr "Président⸱e" -#: sith/settings.py:612 +#: sith/settings.py:610 msgid "Vice-President" msgstr "Vice-Président⸱e" -#: sith/settings.py:613 +#: sith/settings.py:611 msgid "Treasurer" msgstr "Trésorier⸱e" -#: sith/settings.py:614 +#: sith/settings.py:612 msgid "Communication supervisor" msgstr "Responsable communication" -#: sith/settings.py:615 +#: sith/settings.py:613 msgid "Secretary" msgstr "Secrétaire" -#: sith/settings.py:616 +#: sith/settings.py:614 msgid "IT supervisor" msgstr "Responsable info" -#: sith/settings.py:617 +#: sith/settings.py:615 msgid "Board member" msgstr "Membre du bureau" -#: sith/settings.py:618 +#: sith/settings.py:616 msgid "Active member" msgstr "Membre actif⸱ve" -#: sith/settings.py:619 +#: sith/settings.py:617 msgid "Curious" msgstr "Curieux⸱euse" -#: sith/settings.py:656 +#: sith/settings.py:654 msgid "A new poster needs to be moderated" msgstr "Une nouvelle affiche a besoin d'être modérée" -#: sith/settings.py:657 +#: sith/settings.py:655 msgid "A new mailing list needs to be moderated" msgstr "Une nouvelle mailing list a besoin d'être modérée" -#: sith/settings.py:660 +#: sith/settings.py:658 msgid "A new pedagogy comment has been signaled for moderation" msgstr "" "Un nouveau commentaire de la pédagogie a été signalé pour la modération" -#: sith/settings.py:662 +#: sith/settings.py:660 #, python-format msgid "There are %s fresh news to be moderated" msgstr "Il y a %s nouvelles toutes fraîches à modérer" -#: sith/settings.py:663 +#: sith/settings.py:661 msgid "New files to be moderated" msgstr "Nouveaux fichiers à modérer" -#: sith/settings.py:664 +#: sith/settings.py:662 #, python-format msgid "There are %s pictures to be moderated in the SAS" msgstr "Il y a %s photos à modérer dans le SAS" -#: sith/settings.py:665 +#: sith/settings.py:663 msgid "You've been identified on some pictures" msgstr "Vous avez été identifié sur des photos" -#: sith/settings.py:666 +#: sith/settings.py:664 #, python-format msgid "You just refilled of %s €" msgstr "Vous avez rechargé votre compte de %s€" -#: sith/settings.py:667 +#: sith/settings.py:665 #, python-format msgid "You just bought %s" msgstr "Vous avez acheté %s" -#: sith/settings.py:668 +#: sith/settings.py:666 msgid "You have a notification" msgstr "Vous avez une notification" -#: sith/settings.py:680 +#: sith/settings.py:678 msgid "Success!" msgstr "Succès !" -#: sith/settings.py:681 +#: sith/settings.py:679 msgid "Fail!" msgstr "Échec !" -#: sith/settings.py:682 +#: sith/settings.py:680 msgid "You successfully posted an article in the Weekmail" msgstr "Article posté avec succès dans le Weekmail" -#: sith/settings.py:683 +#: sith/settings.py:681 msgid "You successfully edited an article in the Weekmail" msgstr "Article édité avec succès dans le Weekmail" -#: sith/settings.py:684 +#: sith/settings.py:682 msgid "You successfully sent the Weekmail" msgstr "Weekmail envoyé avec succès" -#: sith/settings.py:692 +#: sith/settings.py:690 msgid "AE tee-shirt" msgstr "Tee-shirt AE" diff --git a/sith/settings.py b/sith/settings.py index 56c6c0bc..054787e7 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -432,8 +432,6 @@ SITH_SUBSCRIPTION_LOCATIONS = [ SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")] -SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"} - SITH_COUNTER_BANK = [ ("OTHER", "Autre"), ("SOCIETE-GENERALE", "Société générale"), From ad44fd52a4e8d146baa57ff5e0bb97feb287f64f Mon Sep 17 00:00:00 2001 From: Sli Date: Tue, 17 Dec 2024 10:54:41 +0100 Subject: [PATCH 9/9] Apply review comments --- counter/templates/counter/counter_click.jinja | 6 +++++- locale/fr/LC_MESSAGES/django.po | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/counter/templates/counter/counter_click.jinja b/counter/templates/counter/counter_click.jinja index 96a72435..6de57147 100644 --- a/counter/templates/counter/counter_click.jinja +++ b/counter/templates/counter/counter_click.jinja @@ -111,7 +111,11 @@ {% else %}

- {% trans trimmed %}As a barman, you are not able to refill any account on your own. An admin should be connected on this counter for that. The customer can refill by using the eboutic.{% endtrans %} + {% trans trimmed %} + As a barman, you are not able to refill any account on your own. + An admin should be connected on this counter for that. + The customer can refill by using the eboutic. + {% endtrans %}

{% endif %} diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 6fb47e5b..1f74ddaa 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-17 02:39+0100\n" +"POT-Creation-Date: 2024-12-17 10:53+0100\n" "PO-Revision-Date: 2016-07-18\n" "Last-Translator: Maréchal \n" @@ -3301,7 +3301,7 @@ msgid "Go to my Trombi tools" msgstr "Allez à mes outils de Trombi" #: core/templates/core/user_preferences.jinja:39 -#: counter/templates/counter/counter_click.jinja:119 +#: counter/templates/counter/counter_click.jinja:123 msgid "Student card" msgstr "Carte étudiante" @@ -3978,9 +3978,9 @@ msgid "" "should be connected on this counter for that. The customer can refill by " "using the eboutic." msgstr "" -"En tant que barman, vous n'êtes pas en mesure de recharger un compte par vous même. " -"Un admin doit être connecté sur ce comptoir pour cela. Le client peut recharger son compte " -"en utilisant l'eboutic" +"En tant que barman, vous n'êtes pas en mesure de recharger un compte par " +"vous même. Un admin doit être connecté sur ce comptoir pour cela. Le client " +"peut recharger son compte en utilisant l'eboutic" #: counter/templates/counter/counter_list.jinja:4 #: counter/templates/counter/counter_list.jinja:10