Compare commits

..

9 Commits

Author SHA1 Message Date
TitouanDor 494367b743 ajout translation 2026-05-30 20:15:09 +02:00
TitouanDor edda337d6d modification traduction 2026-05-30 18:02:19 +02:00
TitouanDor 631cfd7fdc ajout lien vers la page des CGV 2026-05-30 18:02:19 +02:00
imperosol 2ee53d201e one CGV button for and etransaction and sith account 2026-05-30 18:02:19 +02:00
TitouanDor b5332e95c7 ajout balise de traduction 2026-05-30 18:02:19 +02:00
TitouanDor c5d04f6f1d supression alert() 2026-05-30 18:02:19 +02:00
TitouanDor 530c9effb2 Juste one CGU checkbox 2026-05-30 18:02:19 +02:00
TitouanDor 4c04f84a25 correction fautes orthographes 2026-05-30 18:02:02 +02:00
TitouanDor 9ad269035d add button for general term and condition 2026-05-30 18:01:47 +02:00
11 changed files with 83 additions and 141 deletions
-24
View File
@@ -392,30 +392,6 @@ class ClubRoleForm(forms.ModelForm):
self.instance.order = cleaned_data["ORDER"] - 1 self.instance.order = cleaned_data["ORDER"] - 1
return cleaned_data return cleaned_data
def save(self, commit=True): # noqa: FBT002
instance: ClubRole = super().save(commit=commit)
if commit and "is_board" in self.changed_data:
# if the role was moved from board to simple member,
# remove all users with that role from the club board group.
# If the role became a board role, add users with
# that role to the club board group.
group_id = instance.club.board_group_id
if self.cleaned_data["is_board"]:
User.groups.through.objects.bulk_create(
[
User.groups.through(user_id=u, group_id=group_id)
for u in Membership.objects.ongoing()
.filter(role=instance)
.values_list("user_id", flat=True)
],
ignore_conflicts=True,
)
else:
User.groups.through.objects.filter(
user__memberships__role=instance, group_id=group_id
).delete()
return instance
class ClubRoleCreateForm(forms.ModelForm): class ClubRoleCreateForm(forms.ModelForm):
"""Form to create a club role. """Form to create a club role.
+1 -28
View File
@@ -4,7 +4,6 @@ import pytest
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now
from model_bakery import baker, seq from model_bakery import baker, seq
from model_bakery.recipe import Recipe from model_bakery.recipe import Recipe
from pytest_django.asserts import assertRedirects from pytest_django.asserts import assertRedirects
@@ -240,7 +239,7 @@ class TestClubRoleUpdate(TestCase):
def test_president_moves_itself_out_of_the_presidency(self): def test_president_moves_itself_out_of_the_presidency(self):
"""Test that if the user moves its own role out of the presidency, """Test that if the user moves its own role out of the presidency,
then it loses access to the update page.""" then it's redirected to another page and loses access to the update page."""
self.payload["roles-0-is_presidency"] = False self.payload["roles-0-is_presidency"] = False
self.client.force_login(self.user) self.client.force_login(self.user)
res = self.client.post(self.url, data=self.payload) res = self.client.post(self.url, data=self.payload)
@@ -252,29 +251,3 @@ class TestClubRoleUpdate(TestCase):
res = self.client.get(self.url) res = self.client.get(self.url)
assert res.status_code == 403 assert res.status_code == 403
def test_role_stops_being_board(self):
"""Test that if a role stops being a board role,
its users lose the club board group."""
self.payload["roles-0-is_board"] = False
self.payload["roles-0-is_presidency"] = False
self.payload["roles-1-is_board"] = False
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
assert formset.is_valid()
formset.save()
assert not self.user.groups.contains(self.club.board_group)
def test_role_becomes_board(self):
"""Test that if a role becomes a board role,
its active users get the club board group"""
members = [
baker.make(Membership, club=self.club, role=self.roles[0], end_date=None),
baker.make(Membership, club=self.club, role=self.roles[0], end_date=now()),
]
self.payload["roles-2-is_board"] = True
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
assert formset.is_valid()
formset.save()
# the second membership is finished, so its user shouldn't get the role
assert members[0].user.groups.contains(self.club.board_group)
assert not members[1].user.groups.contains(self.club.board_group)
-4
View File
@@ -46,10 +46,6 @@ details.accordion>.accordion-content {
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
overflow: hidden; overflow: hidden;
@media screen and (max-width: 600px) {
padding: .75em 1.5em;
}
} }
@mixin animation($selector) { @mixin animation($selector) {
-1
View File
@@ -141,7 +141,6 @@ form {
display: block; display: block;
margin: calc(var(--nf-input-size) * 1.5) auto 10px; margin: calc(var(--nf-input-size) * 1.5) auto 10px;
line-height: 1; line-height: 1;
white-space: nowrap;
.fields-centered { .fields-centered {
padding: 10px 10px 0; padding: 10px 10px 0;
@@ -1,6 +1,6 @@
import type { RecursivePartial, TomSettings } from "tom-select/src/types"; import type { RecursivePartial, TomSettings } from "tom-select/dist/types/types";
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base"; import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base.ts";
import { registerComponent } from "#core:utils/web-components"; import { registerComponent } from "#core:utils/web-components.ts";
const productParsingRegex = /^(\d+x)?(.*)/i; const productParsingRegex = /^(\d+x)?(.*)/i;
const codeParsingRegex = / \((\w+)\)$/; const codeParsingRegex = / \((\w+)\)$/;
@@ -63,6 +63,13 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
); );
}, },
); );
this.widget.hook("after", "onOptionSelect", () => {
/* Focus the next element if it's an input */
if (this.nextElementSibling.nodeName === "INPUT") {
(this.nextElementSibling as HTMLInputElement).focus();
}
});
} }
protected tomSelectSettings(): RecursivePartial<TomSettings> { protected tomSelectSettings(): RecursivePartial<TomSettings> {
/* We disable the dropdown on focus because we're going to always autofocus the widget */ /* We disable the dropdown on focus because we're going to always autofocus the widget */
@@ -73,7 +80,9 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
// We need to manually set weights or it results on an inconsistent // We need to manually set weights or it results on an inconsistent
// behavior between production and development environment // behavior between production and development environment
searchField: [ searchField: [
// @ts-expect-error documentation says it's fine, specified type is wrong
{ field: "code", weight: 2 }, { field: "code", weight: 2 },
// @ts-expect-error documentation says it's fine, specified type is wrong
{ field: "text", weight: 0.5 }, { field: "text", weight: 0.5 },
], ],
}; };
@@ -25,9 +25,6 @@ document.addEventListener("alpine:init", () => {
} }
this.codeField = this.$refs.codeField; this.codeField = this.$refs.codeField;
this.codeField.widget.hook("after", "onOptionSelect", () => {
this.handleCode();
});
this.codeField.widget.focus(); this.codeField.widget.focus();
// It's quite tricky to manually apply attributes to the management part // It's quite tricky to manually apply attributes to the management part
@@ -157,7 +154,6 @@ document.addEventListener("alpine:init", () => {
this.addToBasket(code, quantity); this.addToBasket(code, quantity);
} }
this.codeField.widget.clear(); this.codeField.widget.clear();
this.codeField.widget.setTextboxValue("");
this.codeField.widget.focus(); this.codeField.widget.focus();
}, },
})); }));
+1 -22
View File
@@ -42,28 +42,7 @@
min-width: 350px; min-width: 350px;
ul { ul {
list-style: none; list-style-type: none;
display: flex;
flex-direction: column;
gap: .5rem;
margin-left: 0;
.basket-row {
display: flex;
align-items: center;
gap: 1rem;
.product-name {
flex: 1 2 0;
min-width: 0;
text-wrap: wrap;
}
}
}
form {
margin-top: .5rem;
margin-bottom: .5rem;
} }
} }
+18 -27
View File
@@ -56,15 +56,10 @@
<div class="accordion-content"> <div class="accordion-content">
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %} {% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
<form method="post" action="" @submit.prevent="handleCode"> <form method="post" action=""
class="code_form" @submit.prevent="handleCode">
<counter-product-select <counter-product-select name="code" x-ref="codeField" autofocus required placeholder="{% trans %}Select a product...{% endtrans %}">
name="code"
x-ref="codeField"
autofocus
required
placeholder="{% trans %}Select a product...{% endtrans %}"
>
<option value=""></option> <option value=""></option>
<optgroup label="{% trans %}Operations{% endtrans %}"> <optgroup label="{% trans %}Operations{% endtrans %}">
<option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option> <option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option>
@@ -73,11 +68,13 @@
{%- for category, prices in categories.items() -%} {%- for category, prices in categories.items() -%}
<optgroup label="{{ category }}"> <optgroup label="{{ category }}">
{%- for price in prices -%} {%- for price in prices -%}
<option value="{{ price.id }}">{{ price.full_label }} ({{ price.product.code }})</option> <option value="{{ price.id }}">{{ price.full_label }}</option>
{%- endfor -%} {%- endfor -%}
</optgroup> </optgroup>
{%- endfor -%} {%- endfor -%}
</counter-product-select> </counter-product-select>
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form> </form>
{% for error in form.non_form_errors() %} {% for error in form.non_form_errors() %}
@@ -105,9 +102,7 @@
{{ form.management_form }} {{ form.management_form }}
</div> </div>
<ul> <ul>
<li x-show="getBasketSize() === 0"> <li x-show="getBasketSize() === 0">{% trans %}This basket is empty{% endtrans %}</li>
<em>{% trans %}This basket is empty{% endtrans %}</em>
</li>
<template x-for="(item, index) in Object.values(basket)" :key="item.product.price.id"> <template x-for="(item, index) in Object.values(basket)" :key="item.product.price.id">
<li> <li>
<template x-for="error in item.errors"> <template x-for="error in item.errors">
@@ -115,23 +110,19 @@
</div> </div>
</template> </template>
<div class="basket-row"> <button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
<div> <span class="quantity" x-text="item.quantity"></span>
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button> <button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
<span class="quantity" x-text="item.quantity"></span>
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
</div>
<span class="product-name" x-text="item.product.name"></span> <span x-text="item.product.name"></span> :
<span x-text="`${item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })} €`"></span> <span x-text="item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })"></span>
<span x-show="item.getBonusQuantity() > 0" <span x-show="item.getBonusQuantity() > 0"
x-text="`${item.getBonusQuantity()} x P`"></span> x-text="`${item.getBonusQuantity()} x P`"></span>
<button <button
class="remove-item" class="remove-item"
@click.prevent="removeFromBasket(item.product.price.id)" @click.prevent="removeFromBasket(item.product.price.id)"
><i class="fa fa-trash-can delete-action"></i></button> ><i class="fa fa-trash-can delete-action"></i></button>
</div>
<input <input
type="hidden" type="hidden"
@@ -24,8 +24,8 @@
<thead> <thead>
<tr> <tr>
<td>Article</td> <td>Article</td>
<td>Quantity</td> <td>{% trans %}Quantity{% endtrans %}</td>
<td>Unit price</td> <td>{% trans %}Unit price{% endtrans %}</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -59,11 +59,21 @@
<div @htmx:after-request="fill"> <div @htmx:after-request="fill">
{{ billing_infos_form }} {{ billing_infos_form }}
</div> </div>
{% include "core/base/notifications.jinja" %} {% endif %}
<form {% include "core/base/notifications.jinja" %}
method="post" <form method="post" id="payment-form">
action="{{ settings.SITH_EBOUTIC_ET_URL }}" {# In order to have one CGV button for both payment means,
> there is only one form, which action will be given
the `formaction` attribute of the selected submit input #}
<div class="form-group margin-bottom">
<input type="checkbox" id="cgv" name="cgv" required>
<label for="cgv">
{% trans trimmed %}I have read and I accept{% endtrans %}
<a href="{{ url('core:page', 'cgv') }}">{% trans %}the general terms and conditions{% endtrans%}</a>
{%trans%}of the student association of the UTBM{% endtrans %}
</label>
</div>
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
<template x-for="[key, value] in Object.entries(data)" :key="key"> <template x-for="[key, value] in Object.entries(data)" :key="key">
<input type="hidden" :name="key" :value="value"> <input type="hidden" :name="key" :value="value">
</template> </template>
@@ -77,24 +87,23 @@
:disabled="!isCbAvailable" :disabled="!isCbAvailable"
{% endif %} {% endif %}
class="btn btn-blue" class="btn btn-blue"
formaction="{{ settings.SITH_EBOUTIC_ET_URL }}"
value="{% trans %}Pay with credit card{% endtrans %}" value="{% trans %}Pay with credit card{% endtrans %}"
/> />
</form> {% else %}
{% else %} <div class="alert alert-yellow">
<div class="alert alert-yellow"> {% trans trimmed %}
{% trans trimmed %} Credit card payments are currently disabled on the eboutic.
Credit card payments are currently disabled on the eboutic. You may still refill your account in one of the AE counters.
You may still refill your account in one of the AE counters. Please excuse us for the inconvenience.
Please excuse us for the inconvenience. {% endtrans %}
{% endtrans %} </div>
</div> {% endif %}
{% endif %} {% if basket.contains_refilling_item %}
{% if basket.contains_refilling_item %} <p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p> {% elif basket.total > user.account_balance %}
{% elif basket.total > user.account_balance %} <p>{% trans %}AE account payment disabled because you do not have enough money remaining.{% endtrans %}</p>
<p>{% trans %}AE account payment disabled because you do not have enough money remaining.{% endtrans %}</p> {% else %}
{% else %}
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
{% csrf_token %} {% csrf_token %}
<input <input
{% if basket.is_expired %} {% if basket.is_expired %}
@@ -104,9 +113,10 @@
{% endif %} {% endif %}
class="btn btn-blue" class="btn btn-blue"
type="submit" type="submit"
formaction="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}"
value="{% trans %}Pay with Sith account{% endtrans %}" value="{% trans %}Pay with Sith account{% endtrans %}"
/> />
</form> {% endif %}
{% endif %} </form>
</div> </div>
{% endblock %} {% endblock %}
+15 -2
View File
@@ -6,7 +6,7 @@
msgid "" msgid ""
msgstr "" msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-05-15 11:46+0200\n" "POT-Creation-Date: 2026-05-30 20:12+0200\n"
"PO-Revision-Date: 2016-07-18\n" "PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n" "Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n" "Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -683,6 +683,7 @@ msgstr "Étiquette"
#: core/templates/core/user_account_detail.jinja #: core/templates/core/user_account_detail.jinja
#: core/templates/core/user_stats.jinja #: core/templates/core/user_stats.jinja
#: counter/templates/counter/last_ops.jinja #: counter/templates/counter/last_ops.jinja
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "Quantity" msgid "Quantity"
msgstr "Quantité" msgstr "Quantité"
@@ -966,7 +967,7 @@ msgstr "rôle de club membre"
msgid "Benefit" msgid "Benefit"
msgstr "Bénéfice" msgstr "Bénéfice"
#: club/views.py #: club/views.py eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "Unit price" msgid "Unit price"
msgstr "Prix unitaire" msgstr "Prix unitaire"
@@ -4367,6 +4368,18 @@ msgstr "Solde actuel : "
msgid "Remaining account amount: " msgid "Remaining account amount: "
msgstr "Solde restant : " msgstr "Solde restant : "
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "I have read and I accept"
msgstr "J'ai lu et j'accepte"
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "the general terms and conditions"
msgstr "les conditions générales de vente"
#: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "of the student association of the UTBM"
msgstr "de l'Association des étudiants de l'UTBM"
#: eboutic/templates/eboutic/eboutic_checkout.jinja #: eboutic/templates/eboutic/eboutic_checkout.jinja
msgid "Pay with credit card" msgid "Pay with credit card"
msgstr "Payer avec une carte bancaire" msgstr "Payer avec une carte bancaire"
+1 -1
View File
@@ -68,7 +68,7 @@ dev = [
"pre-commit>=4.6.0,<5.0.0", "pre-commit>=4.6.0,<5.0.0",
"ruff>=0.15.13,<1.0.0", "ruff>=0.15.13,<1.0.0",
"djhtml>=3.0.11,<4.0.0", "djhtml>=3.0.11,<4.0.0",
"faker>=40.20.0,<41.0.0", "faker>=40.18.0,<41.0.0",
"rjsmin>=1.2.5,<2.0.0", "rjsmin>=1.2.5,<2.0.0",
] ]
tests = [ tests = [