diff --git a/apps/note/templates/sheets/order.html b/apps/note/templates/sheets/order.html new file mode 100644 index 00000000..2511c2de --- /dev/null +++ b/apps/note/templates/sheets/order.html @@ -0,0 +1,17 @@ +{% extends "base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load crispy_forms_tags %} +{% load i18n %} + +{% block content %} +
+

+ {{ title }} +

+
+ {% crispy form %} +
+
+{% endblock %} \ No newline at end of file diff --git a/apps/sheets/migrations/0002_auto_20220818_1713.py b/apps/sheets/migrations/0002_auto_20220818_1713.py new file mode 100644 index 00000000..ce27c43c --- /dev/null +++ b/apps/sheets/migrations/0002_auto_20220818_1713.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.27 on 2022-08-18 15:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('sheets', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='order', + name='gift', + ), + migrations.AddField( + model_name='orderedfood', + name='gift', + field=models.IntegerField(default=0, verbose_name='gift'), + preserve_default=False, + ), + migrations.AddField( + model_name='orderedmeal', + name='gift', + field=models.IntegerField(default=0, verbose_name='gift'), + preserve_default=False, + ), + migrations.AlterField( + model_name='orderedfood', + name='status', + field=models.CharField(choices=[('QUEUED', 'queued'), ('READY', 'ready'), ('SERVED', 'served'), ('CANCELED', 'canceled')], default='QUEUED', max_length=8, verbose_name='status'), + ), + ] diff --git a/apps/sheets/models.py b/apps/sheets/models.py index 7ae3bce1..c4c47fa2 100644 --- a/apps/sheets/models.py +++ b/apps/sheets/models.py @@ -137,7 +137,7 @@ class Meal(models.Model): ) def __str__(self): - return self.name + return _("meal").capitalize() + " " + self.name class Meta: verbose_name = _("meal") @@ -162,10 +162,6 @@ class Order(models.Model): auto_now_add=True, ) - gift = models.IntegerField( - verbose_name=_("gift"), - ) - class Meta: verbose_name = _("order") verbose_name_plural = _("orders") @@ -184,6 +180,10 @@ class OrderedMeal(models.Model): verbose_name=_("meal"), ) + gift = models.IntegerField( + verbose_name=_("gift"), + ) + class Meta: verbose_name = _("ordered meal") verbose_name_plural = _("ordered meals") @@ -229,6 +229,10 @@ class OrderedFood(models.Model): verbose_name=_("priority request"), ) + gift = models.IntegerField( + verbose_name=_("gift"), + ) + number = models.IntegerField( verbose_name=_("number"), help_text=_("How many times the user ordered this."), @@ -242,6 +246,7 @@ class OrderedFood(models.Model): ('SERVED', _("served")), ('CANCELED', _("canceled")), ], + default='QUEUED', verbose_name=_("status"), ) @@ -268,14 +273,16 @@ class SheetOrderTransaction(Transaction): return _("note sheet") @property - def price(self): + def get_price(self): if self.ordered_food.meal: - return sum(ordered_food.price + sum(opt.extra_cost for opt in ordered_food.options.all()) - for ordered_food in self.ordered_food.meal.orderedfood_set.exclude(status='CANCELED').all()) + return self.ordered_food.meal.meal.price + self.ordered_food.meal.gift + sum( + sum(opt.extra_cost for opt in ordered_food.options.all()) + for ordered_food in self.ordered_food.meal.orderedfood_set.exclude(status='CANCELED').all()) elif self.ordered_food.status == 'CANCELED': return 0 else: - return self.ordered_food.food.price + sum(opt.extra_cost for opt in self.ordered_food.options.all()) + return self.ordered_food.food.price + self.ordered_food.gift \ + + sum(opt.extra_cost for opt in self.ordered_food.options.all()) class Meta: verbose_name = _("sheet order transaction") diff --git a/apps/sheets/templates/sheets/food_form.html b/apps/sheets/templates/sheets/food_form.html index 0b13f206..e4f12b60 100644 --- a/apps/sheets/templates/sheets/food_form.html +++ b/apps/sheets/templates/sheets/food_form.html @@ -1,11 +1,11 @@ -{% extends "wei/base.html" %} +{% extends "base.html" %} {% comment %} SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} {% load crispy_forms_tags %} {% load i18n %} -{% block profile_content %} +{% block content %}

{{ title }} diff --git a/apps/sheets/templates/sheets/meal_form.html b/apps/sheets/templates/sheets/meal_form.html index c62fec40..f8a3ed08 100644 --- a/apps/sheets/templates/sheets/meal_form.html +++ b/apps/sheets/templates/sheets/meal_form.html @@ -1,11 +1,11 @@ -{% extends "wei/base.html" %} +{% extends "base.html" %} {% comment %} SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} {% load crispy_forms_tags %} {% load i18n %} -{% block profile_content %} +{% block content %}

{{ title }} diff --git a/apps/sheets/templates/sheets/sheet_detail.html b/apps/sheets/templates/sheets/sheet_detail.html index 3163d8e9..04679958 100644 --- a/apps/sheets/templates/sheets/sheet_detail.html +++ b/apps/sheets/templates/sheets/sheet_detail.html @@ -75,7 +75,7 @@

diff --git a/apps/sheets/templates/sheets/sheet_form.html b/apps/sheets/templates/sheets/sheet_form.html index c62fec40..f8a3ed08 100644 --- a/apps/sheets/templates/sheets/sheet_form.html +++ b/apps/sheets/templates/sheets/sheet_form.html @@ -1,11 +1,11 @@ -{% extends "wei/base.html" %} +{% extends "base.html" %} {% comment %} SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} {% load crispy_forms_tags %} {% load i18n %} -{% block profile_content %} +{% block content %}

{{ title }} diff --git a/apps/sheets/urls.py b/apps/sheets/urls.py index 3b7e91ed..0449f93d 100644 --- a/apps/sheets/urls.py +++ b/apps/sheets/urls.py @@ -3,7 +3,7 @@ from django.urls import path -from sheets.views import FoodCreateView, FoodUpdateView, MealCreateView, MealUpdateView, \ +from sheets.views import FoodCreateView, FoodUpdateView, MealCreateView, MealUpdateView, OrderView, \ SheetCreateView, SheetDetailView, SheetListView, SheetUpdateView app_name = 'sheets' @@ -17,4 +17,5 @@ urlpatterns = [ path('food//update/', FoodUpdateView.as_view(), name="food_update"), path('meal/create//', MealCreateView.as_view(), name="meal_create"), path('meal//update/', MealUpdateView.as_view(), name="meal_update"), + path('order//', OrderView.as_view(), name="sheet_order"), ] diff --git a/apps/sheets/views.py b/apps/sheets/views.py index ddf66d98..d7a461f3 100644 --- a/apps/sheets/views.py +++ b/apps/sheets/views.py @@ -1,18 +1,28 @@ # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from datetime import timedelta + +from crispy_forms.bootstrap import Accordion, AccordionGroup, FormActions from crispy_forms.helper import FormHelper +from crispy_forms.layout import Fieldset, Submit, Row, Field +from django import forms from django.contrib.auth.mixins import LoginRequiredMixin +from django.db import transaction +from django.forms import Form from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django.views.generic import DetailView, UpdateView +from django.views.generic import DetailView, UpdateView, FormView from django_tables2 import SingleTableView +from note.models import Alias, Note +from note.templatetags.pretty_money import pretty_money +from note_kfet.inputs import AmountInput, Autocomplete from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms import FoodForm, MealForm, SheetForm, FoodOptionsFormSet, FoodOptionFormSetHelper -from .models import Sheet, Food, Meal +from .models import Sheet, Food, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction from .tables import SheetTable @@ -45,13 +55,13 @@ class SheetCreateView(ProtectQuerysetMixin, ProtectedCreateView): ) -class SheetUpdateView(ProtectQuerysetMixin, UpdateView): +class SheetUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): model = Sheet form_class = SheetForm extra_context = {"title": _("Update note sheet")} -class SheetDetailView(ProtectQuerysetMixin, DetailView): +class SheetDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): model = Sheet def get_context_data(self, **kwargs): @@ -114,7 +124,7 @@ class FoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],)) -class FoodUpdateView(ProtectQuerysetMixin, UpdateView): +class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): model = Food form_class = FoodForm extra_context = {"title": _("Update food")} @@ -176,7 +186,7 @@ class MealCreateView(ProtectQuerysetMixin, ProtectedCreateView): return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,)) -class MealUpdateView(ProtectQuerysetMixin, UpdateView): +class MealUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): model = Meal form_class = MealForm extra_context = {"title": _("Update meal")} @@ -188,3 +198,211 @@ class MealUpdateView(ProtectQuerysetMixin, UpdateView): def get_success_url(self): return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,)) + + +class OrderView(LoginRequiredMixin, FormView, DetailView): + model = Sheet + template_name = 'sheets/order.html' + extra_context = {'title': _("Order now")} + + def get_form(self, form_class=None): + form = Form() + form.helper = FormHelper() + layout_fields = [] + + self.object = self.get_object() + + form.fields['note'] = forms.ModelChoiceField( + queryset=Note.objects.filter(PermissionBackend.filter_queryset(self.request, Note, 'note.view_note')), + label=_("Orderer"), + initial=self.request.user.note, + widget=Autocomplete( + model=Note, + attrs={ + "api_url": "/api/note/note/", + 'placeholder': _("Who orders") + }, + ), + ) + layout_fields.append(Field('note', css_class='is-valid')) + + for meal in self.object.meal_set.filter(available=True).all(): + form.fields[f'meal_{meal.id}_quantity'] = forms.IntegerField( + label=_("Quantity"), + initial=0, + ) + form.fields[f'meal_{meal.id}_gift'] = forms.IntegerField( + label=_("gift").capitalize(), + initial=0, + widget=AmountInput(), + ) + form.fields[f'meal_{meal.id}_remark'] = forms.CharField( + max_length=255, + required=False, + label=_("remark").capitalize(), + help_text=_("Allergies,…"), + ) + form.fields[f'meal_{meal.id}_priority'] = forms.CharField( + max_length=64, + required=False, + label=_("priority request").capitalize(), + help_text=_("Lesson at 13h30,…"), + ) + + ag = AccordionGroup(f"{meal} ({pretty_money(meal.price)})", + Row(Field(f'meal_{meal.id}_quantity', wrapper_class='col-sm-9'), + Field(f'meal_{meal.id}_gift', wrapper_class='col-sm-3')), + Row(Field(f'meal_{meal.id}_remark', wrapper_class='col-sm-9'), + Field(f'meal_{meal.id}_priority', wrapper_class='col-sm-3'))) + + for food in meal.content.filter(available=True).all(): + if food.foodoption_set.count(): + options_fieldset = Fieldset(_("Options for ") + str(food)) + options_row = Row(css_class='ml-0') + for option in food.foodoption_set.filter(available=True).all(): + form.fields[f'meal_{meal.id}_food_{food.id}_option_{option.id}'] = forms.BooleanField( + label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}", + required=False, + ) + options_row.fields.append( + Field(f'meal_{meal.id}_food_{food.id}_option_{option.id}', wrapper_class='col-sm-12')) + options_fieldset.fields.append(options_row) + ag.fields.append(options_fieldset) + + layout_fields.append(ag) + + for food in self.object.food_set.filter(available=True).all(): + form.fields[f'food_{food.id}_quantity'] = forms.IntegerField( + label=_("quantity").capitalize(), + initial=0, + ) + form.fields[f'food_{food.id}_gift'] = forms.IntegerField( + label=_("gift").capitalize(), + initial=0, + widget=AmountInput(), + ) + form.fields[f'food_{food.id}_remark'] = forms.CharField( + max_length=255, + required=False, + label=_("remark").capitalize(), + help_text=_("Allergies,…"), + ) + form.fields[f'food_{food.id}_priority'] = forms.CharField( + max_length=255, + required=False, + label=_("priority request").capitalize(), + help_text=_("Lesson at 13h30,…"), + ) + + ag = AccordionGroup(f"{food} ({pretty_money(food.price)})", + Row(Field(f'food_{food.id}_quantity', wrapper_class='col-sm-9'), + Field(f'food_{food.id}_gift', wrapper_class='col-sm-3')), + Row(Field(f'food_{food.id}_remark', wrapper_class='col-sm-9'), + Field(f'food_{food.id}_priority', wrapper_class='col-sm-3'))) + + if food.foodoption_set.count(): + options_fieldset = Fieldset(_("Options")) + options_row = Row(css_class='ml-0') + for option in food.foodoption_set.all(): + form.fields[f'food_{food.id}_option_{option.id}'] = forms.BooleanField( + label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}", + required=False, + ) + options_row.fields.append(Field(f'food_{food.id}_option_{option.id}', wrapper_class='col-sm-12')) + options_fieldset.fields.append(options_row) + ag.fields.append(options_fieldset) + + layout_fields.append(ag) + + layout_fields.append(FormActions(Submit('submit', _("Order now")))) + + form.helper.layout = Accordion(*layout_fields) + + if self.request.method in ['PUT', 'POST']: + form.data = self.request.POST + form.files = self.request.FILES + form.is_bound = not form.data or not form.files + + return form + + def form_valid(self, form): + data = form.cleaned_data + sheet = self.get_object() + + with transaction.atomic(): + order = Order.objects.create(sheet_id=self.kwargs['pk'], note=data['note']) + + total_quantity = 0 + + for meal in sheet.meal_set.filter(available=True).all(): + quantity = data[f'meal_{meal.id}_quantity'] + if not quantity: + continue + + total_quantity += quantity + gift = data[f'meal_{meal.id}_gift'] + remark = data[f'meal_{meal.id}_remark'] or '' + priority = data[f'meal_{meal.id}_priority'] or '' + ordered_meal = OrderedMeal.objects.create(order=order, meal=meal, gift=gift) + + for ignored in range(quantity): + for food in meal.content.filter(available=True).all(): + n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'], + order__note=order.note, + order__date__gte=timezone.now() - timedelta(hours=6), + food=food).exclude(status='CANCELED').count() + of = OrderedFood.objects.create(order=order, meal=ordered_meal, food=food, + remark=remark, priority=priority, number=n + 1, gift=0) + + for option in food.foodoption_set.filter(available=True).all(): + if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']: + of.options.add(option) + of.save() + + first_food = ordered_meal.orderedfood_set.first() + tr = SheetOrderTransaction(source_id=order.note_id, destination=first_food.food.club.note, + source_alias=str(order.note), destination_alias=first_food.food.club.name, + quantity=quantity, ordered_food=first_food, + reason=f"{meal.name} - {sheet.name}") + tr.amount = tr.get_price / tr.quantity + tr.save() + + for food in sheet.food_set.filter(available=True).all(): + quantity = data[f'food_{food.id}_quantity'] + if not quantity: + continue + + total_quantity += quantity + gift = data[f'food_{meal.id}_gift'] + remark = data[f'food_{meal.id}_remark'] or '' + priority = data[f'food_{meal.id}_priority'] or '' + + for ignored in range(quantity): + n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'], + order__note=order.note, + order__date__gte=timezone.now() - timedelta(hours=6), + food=food).exclude(state='CANCELED').count() + of = OrderedFood.objects.create(order=order, food=food, gift=gift, + remark=remark, priority=priority, number=n + 1) + + for option in food.foodoption_set.filter(available=True).all(): + if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']: + of.options.add(option) + of.options.save() + + tr = SheetOrderTransaction(source_id=order.note_id, destination_id=first_food.club.note, + source_alias=str(order.note), destination_alias=first_food.club.name, + quantity=quantity, ordered_food=of, + reason=f"{food.name} - {sheet.name}") + tr.amount = tr.get_price / tr.quantity + tr.save() + + if total_quantity == 0: + form.add_error(None, _("You didn't select anything.")) + transaction.rollback() + return self.form_invalid(form) + + return super().form_valid(form) + + def get_success_url(self): + return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],)) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 216346b4..0fbaf922 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-08-18 14:49+0200\n" +"POT-Creation-Date: 2022-08-18 17:20+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: elkmaennchen \n" "Language-Team: French \n" @@ -1476,7 +1476,7 @@ msgstr "modèles de transaction" msgid "used alias" msgstr "alias utilisé" -#: apps/note/models/transactions.py:136 +#: apps/note/models/transactions.py:136 apps/sheets/views.py:276 msgid "quantity" msgstr "quantité" @@ -2188,7 +2188,7 @@ msgid "the note sheet will be private until this field is checked." msgstr "la feuille de note restera privée tant que ce champ n'est pas coché." #: apps/sheets/models.py:41 apps/sheets/models.py:54 apps/sheets/models.py:116 -#: apps/sheets/models.py:151 apps/sheets/models.py:268 +#: apps/sheets/models.py:151 apps/sheets/models.py:273 msgid "note sheet" msgstr "feuille de note" @@ -2231,7 +2231,8 @@ msgstr "options de nourriture" msgid "content" msgstr "contenu" -#: apps/sheets/models.py:143 apps/sheets/models.py:184 +#: apps/sheets/models.py:140 apps/sheets/models.py:143 +#: apps/sheets/models.py:180 msgid "meal" msgstr "menu" @@ -2243,19 +2244,20 @@ msgstr "menus" msgid "date" msgstr "date" -#: apps/sheets/models.py:166 -msgid "gift" -msgstr "don" - -#: apps/sheets/models.py:170 apps/sheets/models.py:178 +#: apps/sheets/models.py:166 apps/sheets/models.py:174 #: apps/sheets/models.py:196 msgid "order" msgstr "commande" -#: apps/sheets/models.py:171 +#: apps/sheets/models.py:167 msgid "orders" msgstr "commandes" +#: apps/sheets/models.py:184 apps/sheets/models.py:233 apps/sheets/views.py:235 +#: apps/sheets/views.py:280 +msgid "gift" +msgstr "don" + #: apps/sheets/models.py:188 apps/sheets/models.py:204 msgid "ordered meal" msgstr "menu commandé" @@ -2268,56 +2270,56 @@ msgstr "menus commandés" msgid "options" msgstr "options" -#: apps/sheets/models.py:222 +#: apps/sheets/models.py:222 apps/sheets/views.py:242 apps/sheets/views.py:287 msgid "remark" msgstr "remarques" -#: apps/sheets/models.py:229 +#: apps/sheets/models.py:229 apps/sheets/views.py:248 apps/sheets/views.py:293 msgid "priority request" msgstr "demande de priorité" -#: apps/sheets/models.py:233 +#: apps/sheets/models.py:237 msgid "number" msgstr "numéro" -#: apps/sheets/models.py:234 +#: apps/sheets/models.py:238 msgid "How many times the user ordered this." msgstr "Combien de fois cet⋅te utilisateur⋅rice a commandé ceci." -#: apps/sheets/models.py:240 +#: apps/sheets/models.py:244 msgid "queued" msgstr "en attente" -#: apps/sheets/models.py:241 +#: apps/sheets/models.py:245 msgid "ready" msgstr "prêt" -#: apps/sheets/models.py:242 +#: apps/sheets/models.py:246 msgid "served" msgstr "servi" -#: apps/sheets/models.py:243 +#: apps/sheets/models.py:247 msgid "canceled" msgstr "annulé" -#: apps/sheets/models.py:245 +#: apps/sheets/models.py:250 msgid "status" msgstr "statut" -#: apps/sheets/models.py:251 +#: apps/sheets/models.py:256 msgid "served date" msgstr "date de service" -#: apps/sheets/models.py:255 apps/sheets/models.py:256 -#: apps/sheets/models.py:263 +#: apps/sheets/models.py:260 apps/sheets/models.py:261 +#: apps/sheets/models.py:268 msgid "ordered food" msgstr "nourriture commandée" -#: apps/sheets/models.py:281 +#: apps/sheets/models.py:288 msgid "sheet order transaction" msgstr "transaction de commande sur feuille de note" -#: apps/sheets/models.py:282 +#: apps/sheets/models.py:289 msgid "sheet order transactions" msgstr "transactions de commande sur feuille de note" @@ -2347,7 +2349,8 @@ msgstr "Ajouter un plat" msgid "Add new meal" msgstr "Ajouter un menu" -#: apps/sheets/templates/sheets/sheet_detail.html:79 +#: apps/sheets/templates/sheets/sheet_detail.html:79 apps/sheets/views.py:206 +#: apps/sheets/views.py:317 msgid "Order now" msgstr "Commander maintenant" @@ -2359,34 +2362,70 @@ msgstr "Créer une feuille de note" msgid "Note sheet listing" msgstr "Liste des feuilles de notes" -#: apps/sheets/views.py:23 +#: apps/sheets/views.py:33 msgid "Search note sheet" msgstr "Chercher une feuille de note" -#: apps/sheets/views.py:38 +#: apps/sheets/views.py:48 msgid "Create note sheet" msgstr "Créer une feuille de note" -#: apps/sheets/views.py:51 +#: apps/sheets/views.py:61 msgid "Update note sheet" msgstr "Modifier une feuille de note" -#: apps/sheets/views.py:74 +#: apps/sheets/views.py:84 msgid "Create new food" msgstr "Créer un plat" -#: apps/sheets/views.py:120 +#: apps/sheets/views.py:130 msgid "Update food" msgstr "Modifier un plat" -#: apps/sheets/views.py:157 +#: apps/sheets/views.py:167 msgid "Create new meal" msgstr "Créer un menu" -#: apps/sheets/views.py:182 +#: apps/sheets/views.py:192 msgid "Update meal" msgstr "Modifier un menu" +#: apps/sheets/views.py:217 +#, fuzzy +#| msgid "order" +msgid "Orderer" +msgstr "commande" + +#: apps/sheets/views.py:223 +#, fuzzy +#| msgid "orders" +msgid "Who orders" +msgstr "commandes" + +#: apps/sheets/views.py:231 apps/treasury/models.py:140 +msgid "Quantity" +msgstr "Quantité" + +#: apps/sheets/views.py:243 apps/sheets/views.py:288 +msgid "Allergies,…" +msgstr "Allergies,…" + +#: apps/sheets/views.py:249 apps/sheets/views.py:294 +msgid "Lesson at 13h30,…" +msgstr "Cours à 13h30,…" + +#: apps/sheets/views.py:260 +msgid "Options for " +msgstr "Options pour " + +#: apps/sheets/views.py:304 +msgid "Options" +msgstr "Options" + +#: apps/sheets/views.py:401 +msgid "You didn't select anything." +msgstr "Vous n'avez rien sélectionné." + #: apps/treasury/apps.py:12 note_kfet/templates/base.html:96 msgid "Treasury" msgstr "Trésorerie" @@ -2473,10 +2512,6 @@ msgstr "Facture n°{id}" msgid "Designation" msgstr "Désignation" -#: apps/treasury/models.py:140 -msgid "Quantity" -msgstr "Quantité" - #: apps/treasury/models.py:145 msgid "Unit price" msgstr "Prix unitaire"