Merge pull request #842 from ae-utbm/3dsv2-again

Add the new 3DSv2 fields
This commit is contained in:
thomas girod 2024-09-28 17:59:37 +02:00 committed by GitHub
commit 8ec3074488
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 351 additions and 146 deletions

View File

@ -306,6 +306,12 @@ a:not(.button) {
align-items: center;
text-align: justify;
&.alert-yellow {
background-color: rgb(255, 255, 240);
color: rgb(99, 87, 6);
border: rgb(192, 180, 16) 1px solid;
}
&.alert-green {
background-color: rgb(245, 255, 245);
color: rgb(3, 84, 63);

View File

@ -46,6 +46,7 @@ class CustomerAdmin(SearchModelAdmin):
@admin.register(BillingInfo)
class BillingInfoAdmin(admin.ModelAdmin):
list_display = ("first_name", "last_name", "address_1", "city", "country")
autocomplete_fields = ("customer",)
@admin.register(Counter)

View File

@ -2,6 +2,7 @@ from ajax_select import make_ajax_field
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from django import forms
from django.utils.translation import gettext_lazy as _
from phonenumber_field.widgets import RegionalPhoneNumberWidget
from core.views.forms import NFCTextInput, SelectDate, SelectDateTime
from counter.models import (
@ -26,7 +27,11 @@ class BillingInfoForm(forms.ModelForm):
"zip_code",
"city",
"country",
"phone_number",
]
widgets = {
"phone_number": RegionalPhoneNumberWidget,
}
class StudentCardForm(forms.ModelForm):

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.16 on 2024-09-26 10:28
import phonenumber_field.modelfields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("counter", "0022_alter_product_icon"),
]
operations = [
migrations.AddField(
model_name="billinginfo",
name="phone_number",
field=phonenumber_field.modelfields.PhoneNumberField(
max_length=128, null=True, region=None, verbose_name="Phone number"
),
),
]

View File

@ -34,6 +34,7 @@ from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django_countries.fields import CountryField
from phonenumber_field.modelfields import PhoneNumberField
from accounting.models import CurrencyField
from club.models import Club
@ -176,6 +177,14 @@ class BillingInfo(models.Model):
city = models.CharField(_("City"), max_length=50)
country = CountryField(blank_label=_("Country"))
# This table was created during the A22 semester.
# However, later on, CA asked for the phone number to be added to the billing info.
# As the table was already created, this new field had to be nullable,
# even tough it is required by the bank and shouldn't be null.
# If one day there is no null phone number remaining,
# please make the field non-nullable.
phone_number = PhoneNumberField(_("Phone number"), null=True, blank=False)
def __str__(self):
return f"{self.first_name} {self.last_name}"
@ -192,6 +201,8 @@ class BillingInfo(models.Model):
"ZipCode": self.zip_code,
"City": self.city,
"CountryCode": self.country.numeric, # ISO-3166-1 numeric code
"MobilePhone": self.phone_number.as_national.replace(" ", ""),
"CountryCodeMobilePhone": f"+{self.phone_number.country_code}",
}
}
if self.address_2:

View File

@ -315,6 +315,7 @@ class TestBillingInfo:
"zip_code": "34301",
"city": "Sète",
"country": "FR",
"phone_number": "0612345678",
}
def test_edit_infos(self, client: Client, payload: dict):
@ -356,7 +357,7 @@ class TestBillingInfo:
for key, val in payload.items():
assert getattr(infos, key) == val
def test_invalid_data(self, client: Client, payload):
def test_invalid_data(self, client: Client, payload: dict[str, str]):
user = subscriber_user.make()
client.force_login(user)
# address_1, zip_code and country are missing
@ -391,6 +392,60 @@ class TestBillingInfo:
)
assert response.status_code == expected_code
@pytest.mark.parametrize(
"phone_number",
["+33612345678", "0612345678", "06 12 34 56 78", "06-12-34-56-78"],
)
def test_phone_number_format(
self, client: Client, payload: dict, phone_number: str
):
"""Test that various formats of phone numbers are accepted."""
user = subscriber_user.make()
client.force_login(user)
payload["phone_number"] = phone_number
response = client.put(
reverse("api:put_billing_info", args=[user.id]),
json.dumps(payload),
content_type="application/json",
)
assert response.status_code == 200
infos = BillingInfo.objects.get(customer__user=user)
assert infos.phone_number == "0612345678"
assert infos.phone_number.country_code == 33
def test_foreign_phone_number(self, client: Client, payload: dict):
"""Test that a foreign phone number is accepted."""
user = subscriber_user.make()
client.force_login(user)
payload["phone_number"] = "+49612345678"
response = client.put(
reverse("api:put_billing_info", args=[user.id]),
json.dumps(payload),
content_type="application/json",
)
assert response.status_code == 200
infos = BillingInfo.objects.get(customer__user=user)
assert infos.phone_number.as_national == "06123 45678"
assert infos.phone_number.country_code == 49
@pytest.mark.parametrize(
"phone_number", ["061234567a", "06 12 34 56", "061234567879", "azertyuiop"]
)
def test_invalid_phone_number(
self, client: Client, payload: dict, phone_number: str
):
"""Test that invalid phone numbers are rejected."""
user = subscriber_user.make()
client.force_login(user)
payload["phone_number"] = phone_number
response = client.put(
reverse("api:put_billing_info", args=[user.id]),
json.dumps(payload),
content_type="application/json",
)
assert response.status_code == 422
assert not BillingInfo.objects.filter(customer__user=user).exists()
class TestBarmanConnection(TestCase):
@classmethod

View File

@ -1,6 +1,11 @@
from typing import Annotated
from ninja import ModelSchema, Schema
from pydantic import Field, NonNegativeInt, PositiveInt, TypeAdapter
# from phonenumber_field.phonenumber import PhoneNumber
from pydantic_extra_types.phone_numbers import PhoneNumber, PhoneNumberValidator
from counter.models import BillingInfo
@ -31,3 +36,8 @@ class BillingInfoSchema(ModelSchema):
"country",
]
fields_optional = ["customer"]
# for reasons described in the model, BillingInfo.phone_number
# in nullable, but null values shouldn't be actually allowed,
# so we force the field to be required
phone_number: Annotated[PhoneNumber, PhoneNumberValidator(default_region="FR")]

View File

@ -42,7 +42,16 @@ document.addEventListener("alpine:init", () => {
this.req_state = res.ok
? BillingInfoReqState.SUCCESS
: BillingInfoReqState.FAILURE;
if (res.ok) {
if (res.status === 422) {
const errors = (await res.json())["detail"].map((err) => err["loc"]).flat();
Array.from(form.querySelectorAll("input"))
.filter((elem) => errors.includes(elem.name))
.forEach((elem) => {
elem.setCustomValidity(gettext("Incorrect value"));
elem.reportValidity();
elem.oninput = () => elem.setCustomValidity("");
});
} else if (res.ok) {
Alpine.store("billing_inputs").fill();
}
},

View File

@ -98,13 +98,20 @@
</form>
</div>
<br>
{% if must_fill_billing_infos %}
<p>
<i>
{% trans %}You must fill your billing infos if you want to pay with your credit
card{% endtrans %}
</i>
</p>
{% if billing_infos_state == BillingInfoState.EMPTY %}
<div class="alert alert-yellow">
{% trans %}You must fill your billing infos if you want to pay with your credit
card{% endtrans %}
</div>
{% elif billing_infos_state == BillingInfoState.MISSING_PHONE_NUMBER %}
<div class="alert alert-yellow">
{% trans %}
The Crédit Agricole changed its policy related to the billing
information that must be provided in order to pay with a credit card.
If you want to pay with your credit card, you must add a phone number
to the data you already provided.
{% endtrans %}
</div>
{% endif %}
<form method="post" action="{{ settings.SITH_EBOUTIC_ET_URL }}" name="bank-pay-form">
<template x-data x-for="[key, value] in Object.entries($store.billing_inputs.data)">
@ -113,7 +120,7 @@
<input
type="submit"
id="bank-submit-button"
{% if must_fill_billing_infos %}disabled="disabled"{% endif %}
{% if billing_infos_state != BillingInfoState.VALID %}disabled="disabled"{% endif %}
value="{% trans %}Pay with credit card{% endtrans %}"
/>
</form>

View File

@ -16,6 +16,7 @@
import base64
import json
from datetime import datetime
from enum import Enum
import sentry_sdk
from cryptography.exceptions import InvalidSignature
@ -82,6 +83,12 @@ def payment_result(request, result: str) -> HttpResponse:
return render(request, "eboutic/eboutic_payment_result.jinja", context)
class BillingInfoState(Enum):
VALID = 1
EMPTY = 2
MISSING_PHONE_NUMBER = 3
class EbouticCommand(LoginRequiredMixin, TemplateView):
template_name = "eboutic/eboutic_makecommand.jinja"
basket: Basket
@ -130,9 +137,16 @@ class EbouticCommand(LoginRequiredMixin, TemplateView):
default_billing_info = customer.billing_infos
else:
kwargs["customer_amount"] = None
kwargs["must_fill_billing_infos"] = default_billing_info is None
if not kwargs["must_fill_billing_infos"]:
# the user has already filled its billing_infos, thus we can
# make the enum available in the template
kwargs["BillingInfoState"] = BillingInfoState
if default_billing_info is None:
kwargs["billing_infos_state"] = BillingInfoState.EMPTY
elif default_billing_info.phone_number is None:
kwargs["billing_infos_state"] = BillingInfoState.MISSING_PHONE_NUMBER
else:
kwargs["billing_infos_state"] = BillingInfoState.VALID
if kwargs["billing_infos_state"] == BillingInfoState.VALID:
# the user has already filled all of its billing_infos, thus we can
# get it without expecting an error
kwargs["billing_infos"] = dict(self.basket.get_e_transaction_data())
kwargs["basket"] = self.basket

View File

@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-17 12:21+0200\n"
"POT-Creation-Date: 2024-09-26 17:51+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia <skia@libskia.so>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@ -18,8 +18,8 @@ msgstr ""
#: accounting/models.py:62 accounting/models.py:103 accounting/models.py:136
#: accounting/models.py:203 club/models.py:54 com/models.py:274
#: com/models.py:293 counter/models.py:209 counter/models.py:242
#: counter/models.py:377 forum/models.py:59 launderette/models.py:29
#: com/models.py:293 counter/models.py:213 counter/models.py:246
#: counter/models.py:381 forum/models.py:59 launderette/models.py:29
#: launderette/models.py:84 launderette/models.py:122 stock/models.py:36
#: stock/models.py:57 stock/models.py:97 stock/models.py:125
msgid "name"
@ -66,8 +66,8 @@ msgid "account number"
msgstr "numéro de compte"
#: accounting/models.py:109 accounting/models.py:140 club/models.py:344
#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:265
#: counter/models.py:379 trombi/models.py:210
#: com/models.py:74 com/models.py:259 com/models.py:299 counter/models.py:269
#: counter/models.py:383 trombi/models.py:210
msgid "club"
msgstr "club"
@ -88,12 +88,12 @@ msgstr "Compte club"
msgid "%(club_account)s on %(bank_account)s"
msgstr "%(club_account)s sur %(bank_account)s"
#: accounting/models.py:201 club/models.py:350 counter/models.py:860
#: accounting/models.py:201 club/models.py:350 counter/models.py:864
#: election/models.py:16 launderette/models.py:179
msgid "start date"
msgstr "date de début"
#: accounting/models.py:202 club/models.py:351 counter/models.py:861
#: accounting/models.py:202 club/models.py:351 counter/models.py:865
#: election/models.py:17
msgid "end date"
msgstr "date de fin"
@ -106,8 +106,8 @@ msgstr "est fermé"
msgid "club account"
msgstr "compte club"
#: accounting/models.py:212 accounting/models.py:272 counter/models.py:56
#: counter/models.py:583
#: accounting/models.py:212 accounting/models.py:272 counter/models.py:57
#: counter/models.py:587
msgid "amount"
msgstr "montant"
@ -127,20 +127,20 @@ msgstr "numéro"
msgid "journal"
msgstr "classeur"
#: accounting/models.py:273 core/models.py:940 core/models.py:1442
#: core/models.py:1487 core/models.py:1516 core/models.py:1540
#: counter/models.py:593 counter/models.py:686 counter/models.py:896
#: accounting/models.py:273 core/models.py:940 core/models.py:1460
#: core/models.py:1505 core/models.py:1534 core/models.py:1558
#: counter/models.py:597 counter/models.py:690 counter/models.py:900
#: eboutic/models.py:57 eboutic/models.py:173 forum/models.py:311
#: forum/models.py:412 stock/models.py:96
msgid "date"
msgstr "date"
#: accounting/models.py:274 counter/models.py:211 counter/models.py:897
#: accounting/models.py:274 counter/models.py:215 counter/models.py:901
#: pedagogy/models.py:207 stock/models.py:99
msgid "comment"
msgstr "commentaire"
#: accounting/models.py:276 counter/models.py:595 counter/models.py:688
#: accounting/models.py:276 counter/models.py:599 counter/models.py:692
#: subscription/models.py:56
msgid "payment method"
msgstr "méthode de paiement"
@ -166,8 +166,8 @@ msgid "accounting type"
msgstr "type comptable"
#: accounting/models.py:311 accounting/models.py:450 accounting/models.py:483
#: accounting/models.py:515 core/models.py:1515 core/models.py:1541
#: counter/models.py:652
#: accounting/models.py:515 core/models.py:1533 core/models.py:1559
#: counter/models.py:656
msgid "label"
msgstr "étiquette"
@ -266,7 +266,7 @@ msgstr ""
"Vous devez fournir soit un type comptable simplifié ou un type comptable "
"standard"
#: accounting/models.py:442 counter/models.py:252 pedagogy/models.py:41
#: accounting/models.py:442 counter/models.py:256 pedagogy/models.py:41
msgid "code"
msgstr "code"
@ -370,7 +370,7 @@ msgstr "Compte en banque : "
#: core/templates/core/user_account_detail.jinja:66
#: core/templates/core/user_clubs.jinja:34
#: core/templates/core/user_clubs.jinja:63
#: core/templates/core/user_edit.jinja:57
#: core/templates/core/user_edit.jinja:62
#: core/templates/core/user_preferences.jinja:48
#: counter/templates/counter/last_ops.jinja:35
#: counter/templates/counter/last_ops.jinja:65
@ -968,11 +968,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:153 counter/forms.py:186
#: club/forms.py:153 counter/forms.py:187
msgid "Begin date"
msgstr "Date de début"
#: club/forms.py:156 com/views.py:82 com/views.py:201 counter/forms.py:189
#: club/forms.py:156 com/views.py:82 com/views.py:201 counter/forms.py:190
#: election/views.py:167 subscription/views.py:38
msgid "End date"
msgstr "Date de fin"
@ -1058,7 +1058,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:336 counter/models.py:851 counter/models.py:887
#: club/models.py:336 counter/models.py:855 counter/models.py:891
#: eboutic/models.py:53 eboutic/models.py:169 election/models.py:183
#: launderette/models.py:136 launderette/models.py:198 sas/models.py:270
#: trombi/models.py:206
@ -1070,8 +1070,8 @@ msgstr "nom d'utilisateur"
msgid "role"
msgstr "rôle"
#: club/models.py:358 core/models.py:89 counter/models.py:210
#: counter/models.py:243 election/models.py:13 election/models.py:115
#: club/models.py:358 core/models.py:89 counter/models.py:214
#: counter/models.py:247 election/models.py:13 election/models.py:115
#: election/models.py:188 forum/models.py:60 forum/models.py:244
msgid "description"
msgstr "description"
@ -1440,7 +1440,7 @@ msgstr "résumé"
msgid "content"
msgstr "contenu"
#: com/models.py:71 core/models.py:1485 launderette/models.py:92
#: com/models.py:71 core/models.py:1503 launderette/models.py:92
#: launderette/models.py:130 launderette/models.py:181 stock/models.py:74
#: stock/models.py:129
msgid "type"
@ -2217,7 +2217,7 @@ msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
#: core/templates/core/user_detail.jinja:110
#: core/templates/core/user_detail.jinja:112
#: core/templates/core/user_detail.jinja:113
#: core/templates/core/user_edit.jinja:16
#: core/templates/core/user_edit.jinja:21
#: election/templates/election/election_detail.jinja:132
#: election/templates/election/election_detail.jinja:134
#: forum/templates/forum/macros.jinja:104
@ -2250,7 +2250,7 @@ msgstr "avoir une notification pour chaque rechargement"
msgid "file name"
msgstr "nom du fichier"
#: core/models.py:899 core/models.py:1234
#: core/models.py:899 core/models.py:1252
msgid "parent"
msgstr "parent"
@ -2266,11 +2266,11 @@ msgstr "miniature"
msgid "owner"
msgstr "propriétaire"
#: core/models.py:932 core/models.py:1251 core/views/files.py:223
#: core/models.py:932 core/models.py:1269 core/views/files.py:223
msgid "edit group"
msgstr "groupe d'édition"
#: core/models.py:935 core/models.py:1254 core/views/files.py:226
#: core/models.py:935 core/models.py:1272 core/views/files.py:226
msgid "view group"
msgstr "groupe de vue"
@ -2316,11 +2316,11 @@ msgstr "Un fichier de ce nom existe déjà"
msgid "You must provide a file"
msgstr "Vous devez fournir un fichier"
#: core/models.py:1217
#: core/models.py:1235
msgid "page unix name"
msgstr "nom unix de la page"
#: core/models.py:1223
#: core/models.py:1241
msgid ""
"Enter a valid page name. This value may contain only unaccented letters, "
"numbers and ./+/-/_ characters."
@ -2328,55 +2328,55 @@ msgstr ""
"Entrez un nom de page correct. Uniquement des lettres non accentuées, "
"numéros, et ./+/-/_"
#: core/models.py:1241
#: core/models.py:1259
msgid "page name"
msgstr "nom de la page"
#: core/models.py:1246
#: core/models.py:1264
msgid "owner group"
msgstr "groupe propriétaire"
#: core/models.py:1259
#: core/models.py:1277
msgid "lock user"
msgstr "utilisateur bloquant"
#: core/models.py:1266
#: core/models.py:1284
msgid "lock_timeout"
msgstr "décompte du déblocage"
#: core/models.py:1316
#: core/models.py:1334
msgid "Duplicate page"
msgstr "Une page de ce nom existe déjà"
#: core/models.py:1319
#: core/models.py:1337
msgid "Loop in page tree"
msgstr "Boucle dans l'arborescence des pages"
#: core/models.py:1439
#: core/models.py:1457
msgid "revision"
msgstr "révision"
#: core/models.py:1440
#: core/models.py:1458
msgid "page title"
msgstr "titre de la page"
#: core/models.py:1441
#: core/models.py:1459
msgid "page content"
msgstr "contenu de la page"
#: core/models.py:1482
#: core/models.py:1500
msgid "url"
msgstr "url"
#: core/models.py:1483
#: core/models.py:1501
msgid "param"
msgstr "param"
#: core/models.py:1488
#: core/models.py:1506
msgid "viewed"
msgstr "vue"
#: core/models.py:1546
#: core/models.py:1564
msgid "operation type"
msgstr "type d'opération"
@ -2474,7 +2474,7 @@ msgstr "Forum"
msgid "Gallery"
msgstr "Photos"
#: core/templates/core/base.jinja:225 counter/models.py:387
#: core/templates/core/base.jinja:225 counter/models.py:391
#: counter/templates/counter/counter_list.jinja:11
#: eboutic/templates/eboutic/eboutic_main.jinja:4
#: eboutic/templates/eboutic/eboutic_main.jinja:22
@ -2693,7 +2693,7 @@ msgid "Edit group"
msgstr "Éditer le groupe"
#: core/templates/core/group_edit.jinja:9
#: core/templates/core/user_edit.jinja:268
#: core/templates/core/user_edit.jinja:170
#: core/templates/core/user_group.jinja:13
#: pedagogy/templates/pedagogy/uv_edit.jinja:36
msgid "Update"
@ -3184,39 +3184,35 @@ msgstr "Aucun cadeau donné pour l'instant"
msgid "Edit user"
msgstr "Éditer l'utilisateur"
#: core/templates/core/user_edit.jinja:36
#: core/templates/core/user_edit.jinja:41
msgid "Enable camera"
msgstr "Activer la caméra"
#: core/templates/core/user_edit.jinja:44
#: core/templates/core/user_edit.jinja:49
msgid "Take a picture"
msgstr "Prendre une photo"
#: core/templates/core/user_edit.jinja:64
#: core/templates/core/user_edit.jinja:69
msgid "To edit your profile picture, ask a member of the AE"
msgstr "Pour changer votre photo de profil, demandez à un membre de l'AE"
#: core/templates/core/user_edit.jinja:173
msgid "captured"
msgstr "capturé"
#: core/templates/core/user_edit.jinja:196
#: core/templates/core/user_edit.jinja:98
msgid "Edit user profile"
msgstr "Éditer le profil de l'utilisateur"
#: core/templates/core/user_edit.jinja:258
#: core/templates/core/user_edit.jinja:160
msgid "Change my password"
msgstr "Changer mon mot de passe"
#: core/templates/core/user_edit.jinja:263
#: core/templates/core/user_edit.jinja:165
msgid "Change user password"
msgstr "Changer le mot de passe"
#: core/templates/core/user_edit.jinja:273
#: core/templates/core/user_edit.jinja:175
msgid "Username:"
msgstr "Nom d'utilisateur : "
#: core/templates/core/user_edit.jinja:276
#: core/templates/core/user_edit.jinja:178
msgid "Account number:"
msgstr "Numéro de compte : "
@ -3350,7 +3346,7 @@ msgstr "Achats"
msgid "Product top 10"
msgstr "Top 10 produits"
#: core/templates/core/user_stats.jinja:43 counter/forms.py:200
#: core/templates/core/user_stats.jinja:43 counter/forms.py:201
msgid "Product"
msgstr "Produit"
@ -3395,7 +3391,7 @@ msgstr "Cotisations"
msgid "Subscription stats"
msgstr "Statistiques de cotisation"
#: core/templates/core/user_tools.jinja:48 counter/forms.py:159
#: core/templates/core/user_tools.jinja:48 counter/forms.py:160
#: counter/views.py:728
msgid "Counters"
msgstr "Comptoirs"
@ -3654,7 +3650,7 @@ msgstr "Parrain / Marraine"
msgid "Godchild"
msgstr "Fillot / Fillote"
#: core/views/forms.py:338 counter/forms.py:67 trombi/views.py:149
#: core/views/forms.py:338 counter/forms.py:68 trombi/views.py:149
msgid "Select user"
msgstr "Choisir un utilisateur"
@ -3713,24 +3709,24 @@ msgstr "Photos"
msgid "Galaxy"
msgstr "Galaxie"
#: counter/apps.py:30 counter/models.py:403 counter/models.py:857
#: counter/models.py:893 launderette/models.py:32 stock/models.py:39
#: counter/apps.py:30 counter/models.py:407 counter/models.py:861
#: counter/models.py:897 launderette/models.py:32 stock/models.py:39
msgid "counter"
msgstr "comptoir"
#: counter/forms.py:48
#: counter/forms.py:49
msgid "This UID is invalid"
msgstr "Cet UID est invalide"
#: counter/forms.py:89
#: counter/forms.py:90
msgid "User not found"
msgstr "Utilisateur non trouvé"
#: counter/forms.py:145
#: counter/forms.py:146
msgid "Parent product"
msgstr "Produit parent"
#: counter/forms.py:151
#: counter/forms.py:152
msgid "Buying groups"
msgstr "Groupes d'achat"
@ -3738,165 +3734,169 @@ msgstr "Groupes d'achat"
msgid "Ecocup regularization"
msgstr "Régularization des ecocups"
#: counter/models.py:55
#: counter/models.py:56
msgid "account id"
msgstr "numéro de compte"
#: counter/models.py:57
#: counter/models.py:58
msgid "recorded product"
msgstr "produits consignés"
#: counter/models.py:60
#: counter/models.py:61
msgid "customer"
msgstr "client"
#: counter/models.py:61
#: counter/models.py:62
msgid "customers"
msgstr "clients"
#: counter/models.py:73 counter/views.py:309
#: counter/models.py:74 counter/views.py:309
msgid "Not enough money"
msgstr "Solde insuffisant"
#: counter/models.py:171
#: counter/models.py:172
msgid "First name"
msgstr "Prénom"
#: counter/models.py:172
#: counter/models.py:173
msgid "Last name"
msgstr "Nom de famille"
#: counter/models.py:173
#: counter/models.py:174
msgid "Address 1"
msgstr "Adresse 1"
#: counter/models.py:174
#: counter/models.py:175
msgid "Address 2"
msgstr "Adresse 2"
#: counter/models.py:175
#: counter/models.py:176
msgid "Zip code"
msgstr "Code postal"
#: counter/models.py:176
#: counter/models.py:177
msgid "City"
msgstr "Ville"
#: counter/models.py:177
#: counter/models.py:178
msgid "Country"
msgstr "Pays"
#: counter/models.py:221 counter/models.py:247
#: counter/models.py:179
msgid "Phone number"
msgstr "Numéro de téléphone"
#: counter/models.py:225 counter/models.py:251
msgid "product type"
msgstr "type du produit"
#: counter/models.py:253
#: counter/models.py:257
msgid "purchase price"
msgstr "prix d'achat"
#: counter/models.py:254
#: counter/models.py:258
msgid "selling price"
msgstr "prix de vente"
#: counter/models.py:255
#: counter/models.py:259
msgid "special selling price"
msgstr "prix de vente spécial"
#: counter/models.py:262
#: counter/models.py:266
msgid "icon"
msgstr "icône"
#: counter/models.py:267
#: counter/models.py:271
msgid "limit age"
msgstr "âge limite"
#: counter/models.py:268
#: counter/models.py:272
msgid "tray price"
msgstr "prix plateau"
#: counter/models.py:272
#: counter/models.py:276
msgid "parent product"
msgstr "produit parent"
#: counter/models.py:278
#: counter/models.py:282
msgid "buying groups"
msgstr "groupe d'achat"
#: counter/models.py:280 election/models.py:50
#: counter/models.py:284 election/models.py:50
msgid "archived"
msgstr "archivé"
#: counter/models.py:283 counter/models.py:993
#: counter/models.py:287 counter/models.py:997
msgid "product"
msgstr "produit"
#: counter/models.py:382
#: counter/models.py:386
msgid "products"
msgstr "produits"
#: counter/models.py:385
#: counter/models.py:389
msgid "counter type"
msgstr "type de comptoir"
#: counter/models.py:387
#: counter/models.py:391
msgid "Bar"
msgstr "Bar"
#: counter/models.py:387
#: counter/models.py:391
msgid "Office"
msgstr "Bureau"
#: counter/models.py:390
#: counter/models.py:394
msgid "sellers"
msgstr "vendeurs"
#: counter/models.py:398 launderette/models.py:192
#: counter/models.py:402 launderette/models.py:192
msgid "token"
msgstr "jeton"
#: counter/models.py:601
#: counter/models.py:605
msgid "bank"
msgstr "banque"
#: counter/models.py:603 counter/models.py:693
#: counter/models.py:607 counter/models.py:697
msgid "is validated"
msgstr "est validé"
#: counter/models.py:606
#: counter/models.py:610
msgid "refilling"
msgstr "rechargement"
#: counter/models.py:670 eboutic/models.py:227
#: counter/models.py:674 eboutic/models.py:227
msgid "unit price"
msgstr "prix unitaire"
#: counter/models.py:671 counter/models.py:973 eboutic/models.py:228
#: counter/models.py:675 counter/models.py:977 eboutic/models.py:228
msgid "quantity"
msgstr "quantité"
#: counter/models.py:690
#: counter/models.py:694
msgid "Sith account"
msgstr "Compte utilisateur"
#: counter/models.py:690 sith/settings.py:405 sith/settings.py:410
#: counter/models.py:694 sith/settings.py:405 sith/settings.py:410
#: sith/settings.py:430
msgid "Credit card"
msgstr "Carte bancaire"
#: counter/models.py:696
#: counter/models.py:700
msgid "selling"
msgstr "vente"
#: counter/models.py:800
#: counter/models.py:804
msgid "Unknown event"
msgstr "Événement inconnu"
#: counter/models.py:801
#: counter/models.py:805
#, python-format
msgid "Eticket bought for the event %(event)s"
msgstr "Eticket acheté pour l'événement %(event)s"
#: counter/models.py:803 counter/models.py:826
#: counter/models.py:807 counter/models.py:830
#, python-format
msgid ""
"You bought an eticket for the event %(event)s.\n"
@ -3908,63 +3908,63 @@ msgstr ""
"Vous pouvez également retrouver tous vos e-tickets sur votre page de compte "
"%(url)s."
#: counter/models.py:862
#: counter/models.py:866
msgid "last activity date"
msgstr "dernière activité"
#: counter/models.py:865
#: counter/models.py:869
msgid "permanency"
msgstr "permanence"
#: counter/models.py:898
#: counter/models.py:902
msgid "emptied"
msgstr "coffre vidée"
#: counter/models.py:901
#: counter/models.py:905
msgid "cash register summary"
msgstr "relevé de caisse"
#: counter/models.py:969
#: counter/models.py:973
msgid "cash summary"
msgstr "relevé"
#: counter/models.py:972
#: counter/models.py:976
msgid "value"
msgstr "valeur"
#: counter/models.py:975
#: counter/models.py:979
msgid "check"
msgstr "chèque"
#: counter/models.py:977
#: counter/models.py:981
msgid "True if this is a bank check, else False"
msgstr "Vrai si c'est un chèque, sinon Faux."
#: counter/models.py:981
#: counter/models.py:985
msgid "cash register summary item"
msgstr "élément de relevé de caisse"
#: counter/models.py:997
#: counter/models.py:1001
msgid "banner"
msgstr "bannière"
#: counter/models.py:999
#: counter/models.py:1003
msgid "event date"
msgstr "date de l'événement"
#: counter/models.py:1001
#: counter/models.py:1005
msgid "event title"
msgstr "titre de l'événement"
#: counter/models.py:1003
#: counter/models.py:1007
msgid "secret"
msgstr "secret"
#: counter/models.py:1042
#: counter/models.py:1046
msgid "uid"
msgstr "uid"
#: counter/models.py:1047
#: counter/models.py:1051
msgid "student cards"
msgstr "cartes étudiante"
@ -4439,40 +4439,58 @@ msgstr "Solde restant : "
msgid "Billing information"
msgstr "Informations de facturation"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:104
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:103
msgid ""
"You must fill your billing infos if you want to pay with your credit\n"
" card"
" card"
msgstr ""
"Vous devez renseigner vos coordonnées de facturation si vous voulez payer "
"par carte bancaire"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:117
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:108
msgid ""
"\n"
" The Crédit Agricole changed its policy related to the "
"billing\n"
" information that must be provided in order to pay with a "
"credit card.\n"
" If you want to pay with your credit card, you must add a "
"phone number\n"
" to the data you already provided.\n"
" "
msgstr ""
"\n"
"Le Crédit Agricole a changé sa politique relative aux informations à "
"fournir pour effectuer un paiement par carte bancaire. De ce fait, si vous "
"souhaitez payer par carte, vous devez rajouter un numéro de téléphone aux "
"données que vous aviez déjà fourni."
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:124
msgid "Pay with credit card"
msgstr "Payer avec une carte bancaire"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:122
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:129
msgid ""
"AE account payment disabled because your basket contains refilling items."
msgstr ""
"Paiement par compte AE désactivé parce que votre panier contient des bons de "
"rechargement."
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:124
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:131
msgid ""
"AE account payment disabled because you do not have enough money remaining."
msgstr ""
"Paiement par compte AE désactivé parce que votre solde est insuffisant."
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:129
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:136
msgid "Pay with Sith account"
msgstr "Payer avec un compte AE"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:140
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:147
msgid "Billing info registration success"
msgstr "Informations de facturation enregistrées"
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:141
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:148
msgid "Billing info registration failure"
msgstr "Echec de l'enregistrement des informations de facturation."
@ -6325,3 +6343,6 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
#, python-format
msgid "Maximum characters: %(max_length)s"
msgstr "Nombre de caractères max: %(max_length)s"
#~ msgid "captured"
#~ msgstr "capturé"

View File

@ -7,7 +7,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-09-03 15:22+0200\n"
"POT-Creation-Date: 2024-09-27 22:32+0200\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@ -16,9 +16,24 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: core/static/user/js/family_graph.js:230
#: core/static/user/js/family_graph.js:233
msgid "family_tree.%(extension)s"
msgstr "arbre_genealogique.%(extension)s"
#: sas/static/sas/js/picture.js:52
#: core/static/user/js/user_edit.js:93
#, javascript-format
msgid "captured.%s"
msgstr "capture.%s"
#: eboutic/static/eboutic/js/makecommand.js:50
msgid "Incorrect value"
msgstr "Valeur incorrecte"
#: sas/static/sas/js/viewer.js:196
msgid "Couldn't moderate picture"
msgstr "Echec de la suppression de la photo"
#: sas/static/sas/js/viewer.js:209
msgid "Couldn't delete picture"
msgstr "Echec de la suppression de la photo"

29
poetry.lock generated
View File

@ -1813,6 +1813,33 @@ files = [
[package.dependencies]
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-extra-types"
version = "2.9.0"
description = "Extra Pydantic types."
optional = false
python-versions = ">=3.8"
files = []
develop = false
[package.dependencies]
pydantic = ">=2.5.2"
typing-extensions = "*"
[package.extras]
all = ["pendulum (>=3.0.0,<4.0.0)", "phonenumbers (>=8,<9)", "pycountry (>=23)", "python-ulid (>=1,<2)", "python-ulid (>=1,<3)", "pytz (>=2024.1)", "semver (>=3.0.2)", "semver (>=3.0.2,<3.1.0)", "tzdata (>=2024.1)"]
pendulum = ["pendulum (>=3.0.0,<4.0.0)"]
phonenumbers = ["phonenumbers (>=8,<9)"]
pycountry = ["pycountry (>=23)"]
python-ulid = ["python-ulid (>=1,<2)", "python-ulid (>=1,<3)"]
semver = ["semver (>=3.0.2)"]
[package.source]
type = "git"
url = "https://github.com/pydantic/pydantic-extra-types.git"
reference = "HEAD"
resolved_reference = "58db4b096d7c90566d3d48d51b4665c01a591df6"
[[package]]
name = "pygments"
version = "2.18.0"
@ -2626,4 +2653,4 @@ filelock = ">=3.4"
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "b6202203d272cecdb607ea8ebc1ba12dd8369e4f387f65692c4a9681915e6f48"
content-hash = "c9c49497cc576b24c96ea914b74ef5c3a0c2981c488a599752f05aabb575f8d8"

View File

@ -45,6 +45,10 @@ dict2xml = "^1.7.3"
Sphinx = "^5" # Needed for building xapian
tomli = "^2.0.1"
django-honeypot = "^1.2.1"
# When I introduced pydantic-extra-types, I needed *right now*
# the PhoneNumberValidator class which was on the master branch but not released yet.
# Once it's released, switch this to a regular version.
pydantic-extra-types = { git = "https://github.com/pydantic/pydantic-extra-types.git", rev = "58db4b0" }
[tool.poetry.group.prod.dependencies]
# deps used in prod, but unnecessary for development