mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-26 05:23:18 +01:00 
			
		
		
		
	Compare commits
	
		
			6 Commits
		
	
	
		
			033ee010f8
			...
			sheets
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51d60d064c | |||
| 45334e4e02 | |||
| 5174c84b33 | |||
| 51e5e3669e | |||
| 44994a3ae7 | |||
| ba017c38c0 | 
| @@ -26,6 +26,10 @@ if "note" in settings.INSTALLED_APPS: | ||||
|     from note.api.urls import register_note_urls | ||||
|     register_note_urls(router, 'note') | ||||
|  | ||||
| if "sheets" in settings.INSTALLED_APPS: | ||||
|     from sheets.api.urls import register_sheets_urls | ||||
|     register_sheets_urls(router, 'sheets') | ||||
|  | ||||
| if "treasury" in settings.INSTALLED_APPS: | ||||
|     from treasury.api.urls import register_treasury_urls | ||||
|     register_treasury_urls(router, 'treasury') | ||||
|   | ||||
							
								
								
									
										18
									
								
								apps/member/migrations/0009_auto_20220818_1301.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/member/migrations/0009_auto_20220818_1301.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # Generated by Django 2.2.27 on 2022-08-18 11:01 | ||||
|  | ||||
| from django.db import migrations, models | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('member', '0008_auto_20211005_1544'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.AlterField( | ||||
|             model_name='profile', | ||||
|             name='promotion', | ||||
|             field=models.PositiveSmallIntegerField(default=2022, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										17
									
								
								apps/note/templates/sheets/order.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								apps/note/templates/sheets/order.html
									
									
									
									
									
										Normal file
									
								
							| @@ -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 %} | ||||
| <div class="card bg-light mb-3"> | ||||
|     <h3 class="card-header text-center"> | ||||
|         {{ title }} | ||||
|     </h3> | ||||
|     <div class="card-body"> | ||||
|         {% crispy form %} | ||||
|     </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										88
									
								
								apps/note/templates/sheets/waiting_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								apps/note/templates/sheets/waiting_list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="card"> | ||||
| <div class="card-header text-center"> | ||||
|     <h1>{{ food.name }}</h1> | ||||
| </div> | ||||
| <div class="card-body"> | ||||
|     <div class="row"> | ||||
|         <div class="card col-xl-6"> | ||||
|             <div class="card-header text-center"> | ||||
|                 <h2>{% trans "queued"|capfirst %}{% if queue %} ({{ queue|length }}){% endif %}</h2> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|                 <ul> | ||||
|                     {% for ordered_food in queue %} | ||||
|                         <li> | ||||
|                             {{ ordered_food.order.note }} | ||||
|                             {% if ordered_food.priority %} | ||||
|                                 <span class="badge badge-secondary">{{ ordered_food.priority }}</span> | ||||
|                             {% endif %} | ||||
|                         </li> | ||||
|                     {% empty %} | ||||
|                         <div class="alert alert-warning"> | ||||
|                             {% trans "There is no queued order." %} | ||||
|                         </div> | ||||
|                     {% endfor %} | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="card col-xl-6"> | ||||
|             <div class="card-header text-center"> | ||||
|                 <h2>{% trans "ready"|capfirst %}</h2> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|                 <ul> | ||||
|                     {% for ordered_food in ready %} | ||||
|                         <li>{{ ordered_food.order.note }}</li> | ||||
|                     {% empty %} | ||||
|                         <div class="alert alert-warning"> | ||||
|                             {% trans "There is no ready order." %} | ||||
|                         </div> | ||||
|                     {% endfor %} | ||||
|                 </ul> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <hr> | ||||
|  | ||||
|     <h3>{% trans "Other waiting lists:" %}</h3> | ||||
|     <ul> | ||||
|         {% for other_food in food.sheet.food_set.all %} | ||||
|             {% if other_food != food %} | ||||
|                 <li> | ||||
|                     <a href="{% url 'sheets:waiting_list' pk=other_food.pk %}">{{ other_food }}</a> | ||||
|                 </li> | ||||
|             {% endif %} | ||||
|         {% endfor %} | ||||
|     </ul> | ||||
| </div> | ||||
| <div class="card-footer text-center"> | ||||
|     <a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary"> | ||||
|         {% trans "Queued orders" %} | ||||
|     </a> | ||||
|     <a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-primary"> | ||||
|         {% trans "Ready orders" %} | ||||
|     </a> | ||||
|     <a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary"> | ||||
|         {% trans "Back to note sheet detail" %} | ||||
|     </a> | ||||
| </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block extrajavascript %} | ||||
|     <script> | ||||
|         function reload() { | ||||
|             reloadWithTurbolinks() | ||||
|             timeout = setTimeout(reload, 15000) | ||||
|         } | ||||
|  | ||||
|         if (timeout === undefined) | ||||
|             var timeout = setTimeout(reload, 15000) | ||||
|     </script> | ||||
| {% endblock %} | ||||
							
								
								
									
										152
									
								
								apps/note/templates/sheets/waiting_list_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								apps/note/templates/sheets/waiting_list_detail.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="card"> | ||||
| <div class="card-header text-center"> | ||||
|     <h1>{{ title }}</h1> | ||||
| </div> | ||||
| <div class="card-body"> | ||||
|     {% for of in orders %} | ||||
|         <div class="card mb-4"> | ||||
|             <div class="card-header text-center"> | ||||
|                 <h3>{{ of.order.note }}</h3> | ||||
|             </div> | ||||
|             <div class="card-body"> | ||||
|                 <dl class="row"> | ||||
|                     <dt class="col-xl-3">{% trans 'date'|capfirst %}</dt> | ||||
|                     <dd class="col-xl-9">{{ of.order.date }}</dd> | ||||
|  | ||||
|                     {% if of.number > 1 %} | ||||
|                         <dt class="col-xl-3">{% trans 'order number'|capfirst %}</dt> | ||||
|                         <dd class="col-xl-9">{{ of.number }}</dd> | ||||
|                     {% endif %} | ||||
|  | ||||
|                     {% if of.priority %} | ||||
|                         <dt class="col-xl-3">{% trans 'priority request'|capfirst %}</dt> | ||||
|                         <dd class="col-xl-9">{{ of.priority }}</dd> | ||||
|                     {% endif %} | ||||
|  | ||||
|                     {% if of.remark %} | ||||
|                         <dt class="col-xl-3">{% trans 'remark'|capfirst %}</dt> | ||||
|                         <dd class="col-xl-9">{{ of.remark }}</dd> | ||||
|                     {% endif %} | ||||
|  | ||||
|                     {% if of.options.count %} | ||||
|                         <dt class="col-xl-3">{% trans 'options'|capfirst %}</dt> | ||||
|                         <dd class="col-xl-9">{{ of.options.all|join:', ' }}</dd> | ||||
|                     {% endif %} | ||||
|                 </dl> | ||||
|             </div> | ||||
|             <div class="card-footer text-center"> | ||||
|                 {% if list_type != 'READY' %} | ||||
|                     <a href="#" class="btn btn-success" onclick="setOrderStatus({{ of.pk }}, 'READY')"> | ||||
|                         {% trans "Mark as ready" %} | ||||
|                     </a> | ||||
|                 {% endif %} | ||||
|                 {% if list_type != 'SERVED' %} | ||||
|                     <a href="#" class="btn btn-primary" onclick="setOrderStatus({{ of.pk }}, 'SERVED')"> | ||||
|                         {% trans "Mark as served" %} | ||||
|                     </a> | ||||
|                 {% endif %} | ||||
|                 {% if list_type != 'QUEUED' %} | ||||
|                     <a href="#" class="btn btn-warning" onclick="setOrderStatus({{ of.pk }}, 'QUEUED')"> | ||||
|                         {% trans "Re-queue" %} | ||||
|                     </a> | ||||
|                 {% endif %} | ||||
|                 {% if list_type != 'CANCELED' %} | ||||
|                     <a href="#" class="btn btn-danger" onclick="setOrderStatus({{ of.pk }}, 'CANCELED')"> | ||||
|                         {% trans "Cancel" %} | ||||
|                     </a> | ||||
|                 {% endif %} | ||||
|             </div> | ||||
|         </div> | ||||
|     {% empty %} | ||||
|         <div class="alert alert-warning"> | ||||
|             {% trans "There is no queued order." %} | ||||
|         </div> | ||||
|     {% endfor %} | ||||
| </div> | ||||
| </div> | ||||
|  | ||||
| <div class="card mt-5"> | ||||
| <div class="card-body"> | ||||
|     <h3>{% trans "Other waiting lists:" %}</h3> | ||||
|     <ul> | ||||
|         {% for other_food in food.sheet.food_set.all %} | ||||
|             {% if other_food != food %} | ||||
|                 <li> | ||||
|                     {% if list_type == 'QUEUED' %} | ||||
|                         <a href="{% url 'sheets:queued_list' pk=other_food.pk %}">{{ other_food }}</a> | ||||
|                     {% else %} | ||||
|                         <a href="{% url 'sheets:ready_list' pk=other_food.pk %}">{{ other_food }}</a> | ||||
|                     {% endif %} | ||||
|                 </li> | ||||
|             {% endif %} | ||||
|         {% endfor %} | ||||
|     </ul> | ||||
| </div> | ||||
| <div class="card-footer text-center"> | ||||
|     {% if list_type != 'QUEUED' %} | ||||
|         <a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary"> | ||||
|             {% trans "Queued orders" %} | ||||
|         </a> | ||||
|     {% endif %} | ||||
|     {% if list_type != 'READY' %} | ||||
|         <a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-success"> | ||||
|             {% trans "Ready orders" %} | ||||
|         </a> | ||||
|     {% endif %} | ||||
|     {% if list_type != 'SERVED' %} | ||||
|         <a href="{% url 'sheets:served_list' pk=food.pk %}" class="btn btn-secondary"> | ||||
|             {% trans "Served orders" %} | ||||
|         </a> | ||||
|     {% endif %} | ||||
|     {% if list_type != 'CANCELED' %} | ||||
|         <a href="{% url 'sheets:canceled_list' pk=food.pk %}" class="btn btn-danger"> | ||||
|             {% trans "Canceled orders" %} | ||||
|         </a> | ||||
|     {% endif %} | ||||
|     <a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="btn btn-primary"> | ||||
|         {% trans "Waiting list" %} | ||||
|     </a> | ||||
|     <a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary"> | ||||
|         {% trans "Back to note sheet detail" %} | ||||
|     </a> | ||||
| </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block extrajavascript %} | ||||
|     <script> | ||||
|         function reload() { | ||||
|             reloadWithTurbolinks() | ||||
|             timeout = setTimeout(reload, 15000) | ||||
|         } | ||||
|  | ||||
|         if (timeout === undefined) | ||||
|             var timeout = setTimeout(reload, 15000) | ||||
|  | ||||
|         function setOrderStatus(ordered_food_id, status) { | ||||
|             fetch('/api/sheets/orderedfood/' + ordered_food_id + '/', { | ||||
|                 method: 'PATCH', | ||||
|                 body: JSON.stringify({ | ||||
|                     status: status, | ||||
|                     served_date: status === 'QUEUED' ? null : new Date().toISOString(), | ||||
|                 }), | ||||
|                 headers: { | ||||
|                     'Content-Type': "application/json; charset=UTF-8", | ||||
|                     'X-CSRFTOKEN': "{{ csrf_token }}" | ||||
|                 } | ||||
|             }).then(response => response.json()).then(response => { | ||||
|                 if ('detail' in response) | ||||
|                     addMsg("{% trans "An error occurred" %}" + " : " + response['detail'], "danger") | ||||
|                 else { | ||||
|                     clearTimeout(timeout) | ||||
|                     reload() | ||||
|                 } | ||||
|             }) | ||||
|         } | ||||
|     </script> | ||||
| {% endblock %} | ||||
| @@ -2928,7 +2928,7 @@ | ||||
| 				"application" | ||||
| 			], | ||||
| 			"query": "{\"user\": [\"user\"]}", | ||||
| 			"type": "create", | ||||
| 			"type": "add", | ||||
| 			"mask": 1, | ||||
| 			"field": "", | ||||
| 			"permanent": true, | ||||
|   | ||||
							
								
								
									
										4
									
								
								apps/sheets/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								apps/sheets/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| default_app_config = 'sheets.apps.SheetsConfig' | ||||
							
								
								
									
										46
									
								
								apps/sheets/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								apps/sheets/admin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib import admin | ||||
| from note_kfet.admin import admin_site | ||||
| from sheets.models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction | ||||
|  | ||||
|  | ||||
| @admin.register(Sheet, site=admin_site) | ||||
| class SheetAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(Food, site=admin_site) | ||||
| class FoodAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(FoodOption, site=admin_site) | ||||
| class FoodOptionAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(Meal, site=admin_site) | ||||
| class MealAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(Order, site=admin_site) | ||||
| class OrderAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(OrderedMeal, site=admin_site) | ||||
| class OrderedMealAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(OrderedFood, site=admin_site) | ||||
| class OrderedFoodAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @admin.register(SheetOrderTransaction, site=admin_site) | ||||
| class SheetOrderTransactionAdmin(admin.ModelAdmin): | ||||
|     pass | ||||
							
								
								
									
										0
									
								
								apps/sheets/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/sheets/api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										55
									
								
								apps/sheets/api/serializers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								apps/sheets/api/serializers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from rest_framework import serializers | ||||
|  | ||||
|  | ||||
| from ..models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction | ||||
|  | ||||
|  | ||||
| class SheetSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Sheet | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class FoodSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Food | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class FoodOptionSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = FoodOption | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class MealSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Meal | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class OrderSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = Order | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class OrderedMealSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = OrderedMeal | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class OrderedFoodSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = OrderedFood | ||||
|         fields = '__all__' | ||||
|  | ||||
|  | ||||
| class SheetOrderTransactionSerializer(serializers.ModelSerializer): | ||||
|     class Meta: | ||||
|         model = SheetOrderTransaction | ||||
|         fields = '__all__' | ||||
							
								
								
									
										19
									
								
								apps/sheets/api/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								apps/sheets/api/urls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from sheets.api.views import SheetViewSet, FoodViewSet, FoodOptionViewSet, MealViewSet, OrderViewSet, \ | ||||
|     OrderedMealViewSet, OrderedFoodViewSet, SheetOrderTransactionViewSet | ||||
|  | ||||
|  | ||||
| def register_sheets_urls(router, path): | ||||
|     """ | ||||
|     Configure router for Sheets REST API. | ||||
|     """ | ||||
|     router.register(path + '/sheet', SheetViewSet) | ||||
|     router.register(path + '/food', FoodViewSet) | ||||
|     router.register(path + '/foodoption', FoodOptionViewSet) | ||||
|     router.register(path + '/meal', MealViewSet) | ||||
|     router.register(path + '/order', OrderViewSet) | ||||
|     router.register(path + '/orderedmeal', OrderedMealViewSet) | ||||
|     router.register(path + '/orderedfood', OrderedFoodViewSet) | ||||
|     router.register(path + '/sheetordertransaction', SheetOrderTransactionViewSet) | ||||
							
								
								
									
										78
									
								
								apps/sheets/api/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								apps/sheets/api/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from api.viewsets import ReadProtectedModelViewSet | ||||
| from django_filters.rest_framework import DjangoFilterBackend | ||||
| from rest_framework.filters import SearchFilter, OrderingFilter | ||||
|  | ||||
| from .serializers import SheetSerializer, FoodSerializer, FoodOptionSerializer, MealSerializer, OrderSerializer, \ | ||||
|     OrderedMealSerializer, OrderedFoodSerializer, SheetOrderTransactionSerializer | ||||
| from ..models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction | ||||
|  | ||||
|  | ||||
| class SheetViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = Sheet.objects.order_by('id') | ||||
|     serializer_class = SheetSerializer | ||||
|     filter_backends = [DjangoFilterBackend, SearchFilter] | ||||
|     filterset_fields = ['name', 'date', ] | ||||
|     search_fields = ['$name', ] | ||||
|  | ||||
|  | ||||
| class FoodViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = Food.objects.order_by('id') | ||||
|     serializer_class = FoodSerializer | ||||
|     filter_backends = [DjangoFilterBackend, SearchFilter] | ||||
|     filterset_fields = ['name', 'sheet', 'price', 'club', 'available', ] | ||||
|     search_fields = ['$name', ] | ||||
|  | ||||
|  | ||||
| class FoodOptionViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = FoodOption.objects.order_by('id') | ||||
|     serializer_class = FoodOptionSerializer | ||||
|     filter_backends = [DjangoFilterBackend, SearchFilter] | ||||
|     filterset_fields = ['name', 'food', 'extra_cost', 'available', ] | ||||
|     search_fields = ['$name', '$food__name', ] | ||||
|  | ||||
|  | ||||
| class MealViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = Meal.objects.order_by('id') | ||||
|     serializer_class = MealSerializer | ||||
|     filter_backends = [DjangoFilterBackend, SearchFilter] | ||||
|     filterset_fields = ['name', 'content', 'price', 'available', ] | ||||
|     search_fields = ['$name', ] | ||||
|  | ||||
|  | ||||
| class OrderViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = Order.objects.order_by('id') | ||||
|     serializer_class = OrderSerializer | ||||
|     filter_backends = [DjangoFilterBackend, SearchFilter] | ||||
|     filterset_fields = ['sheet', 'note', 'date', 'gift', ] | ||||
|     search_fields = ['$sheet__name', '$note__alias__name', '$note__alias__normalized_name', ] | ||||
|  | ||||
|  | ||||
| class OrderedMealViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = OrderedMeal.objects.order_by('id') | ||||
|     serializer_class = OrderedMealSerializer | ||||
|     filter_backends = [DjangoFilterBackend] | ||||
|     filterset_fields = ['order', 'meal', ] | ||||
|  | ||||
|  | ||||
| class OrderedFoodViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = OrderedFood.objects.order_by('id') | ||||
|     serializer_class = OrderedFoodSerializer | ||||
|     filter_backends = [DjangoFilterBackend] | ||||
|     filterset_fields = ['order', 'meal', 'food', 'options', 'number', 'status', 'served_date', ] | ||||
|  | ||||
|  | ||||
| class SheetOrderTransactionViewSet(ReadProtectedModelViewSet): | ||||
|     queryset = SheetOrderTransaction.objects.order_by('-created_at') | ||||
|     serializer_class = SheetOrderTransactionSerializer | ||||
|     filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] | ||||
|     filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name', | ||||
|                         'destination', 'destination_alias', 'destination__alias__name', | ||||
|                         'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount', | ||||
|                         'created_at', 'valid', 'invalidity_reason', 'ordered_food', ] | ||||
|     search_fields = ['$reason', '$source_alias', '$source__alias__name', '$source__alias__normalized_name', | ||||
|                      '$destination_alias', '$destination__alias__name', '$destination__alias__normalized_name', | ||||
|                      '$invalidity_reason', ] | ||||
|     ordering_fields = ['created_at', 'amount', ] | ||||
							
								
								
									
										10
									
								
								apps/sheets/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								apps/sheets/apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.apps import AppConfig | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
|  | ||||
| class SheetsConfig(AppConfig): | ||||
|     name = 'sheets' | ||||
|     verbose_name = _('note sheets') | ||||
							
								
								
									
										67
									
								
								apps/sheets/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								apps/sheets/forms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,67 @@ | ||||
| # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
| from crispy_forms.helper import FormHelper | ||||
| from django import forms | ||||
|  | ||||
| from member.models import Club | ||||
| from note_kfet.inputs import AmountInput, Autocomplete, DateTimePickerInput | ||||
|  | ||||
| from .models import Food, FoodOption, Meal, Sheet | ||||
|  | ||||
|  | ||||
| class SheetForm(forms.ModelForm): | ||||
|     class Meta: | ||||
|         model = Sheet | ||||
|         fields = '__all__' | ||||
|         widgets = { | ||||
|             'date': DateTimePickerInput(), | ||||
|         } | ||||
|  | ||||
|  | ||||
| class FoodForm(forms.ModelForm): | ||||
|     class Meta: | ||||
|         model = Food | ||||
|         exclude = ('sheet', ) | ||||
|         widgets = { | ||||
|             'price': AmountInput(), | ||||
|             'club': Autocomplete( | ||||
|                 model=Club, | ||||
|                 attrs={"api_url": "/api/members/club/"}, | ||||
|             ), | ||||
|         } | ||||
|  | ||||
|  | ||||
| class FoodOptionForm(forms.ModelForm): | ||||
|     class Meta: | ||||
|         model = FoodOption | ||||
|         fields = '__all__' | ||||
|         widgets = { | ||||
|             'extra_cost': AmountInput(), | ||||
|         } | ||||
|  | ||||
|  | ||||
| FoodOptionsFormSet = forms.inlineformset_factory( | ||||
|     Food, | ||||
|     FoodOption, | ||||
|     form=FoodOptionForm, | ||||
|     extra=0, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class FoodOptionFormSetHelper(FormHelper): | ||||
|     def __init__(self, form=None): | ||||
|         super().__init__(form) | ||||
|         self.form_tag = False | ||||
|         self.form_method = 'POST' | ||||
|         self.form_class = 'form-inline' | ||||
|         self.template = 'bootstrap4/table_inline_formset.html' | ||||
|  | ||||
|  | ||||
| class MealForm(forms.ModelForm): | ||||
|     class Meta: | ||||
|         model = Meal | ||||
|         exclude = ('sheet', ) | ||||
|         widgets = { | ||||
|             'content': forms.CheckboxSelectMultiple(), | ||||
|             'price': AmountInput(), | ||||
|         } | ||||
							
								
								
									
										157
									
								
								apps/sheets/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								apps/sheets/migrations/0001_initial.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| # Generated by Django 2.2.27 on 2022-08-18 11:01 | ||||
|  | ||||
| from django.db import migrations, models | ||||
| import django.db.models.deletion | ||||
| import django.utils.timezone | ||||
|  | ||||
|  | ||||
| class Migration(migrations.Migration): | ||||
|  | ||||
|     initial = True | ||||
|  | ||||
|     dependencies = [ | ||||
|         ('member', '0009_auto_20220818_1301'), | ||||
|         ('note', '0006_trust'), | ||||
|     ] | ||||
|  | ||||
|     operations = [ | ||||
|         migrations.CreateModel( | ||||
|             name='Food', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('name', models.CharField(max_length=255, verbose_name='food')), | ||||
|                 ('price', models.IntegerField(verbose_name='price')), | ||||
|                 ('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')), | ||||
|                 ('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='destination club')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'food', | ||||
|                 'verbose_name_plural': 'food', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='FoodOption', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('name', models.CharField(max_length=255, verbose_name='name')), | ||||
|                 ('extra_cost', models.IntegerField(default=0, verbose_name='extra cost')), | ||||
|                 ('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')), | ||||
|                 ('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Food', verbose_name='food')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'food option', | ||||
|                 'verbose_name_plural': 'food options', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='Meal', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('name', models.CharField(max_length=255, verbose_name='name')), | ||||
|                 ('price', models.IntegerField(verbose_name='price')), | ||||
|                 ('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')), | ||||
|                 ('content', models.ManyToManyField(to='sheets.Food', verbose_name='content')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'meal', | ||||
|                 'verbose_name_plural': 'meals', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='Order', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('date', models.DateTimeField(auto_now_add=True, verbose_name='date')), | ||||
|                 ('gift', models.IntegerField(verbose_name='gift')), | ||||
|                 ('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note', verbose_name='note')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'order', | ||||
|                 'verbose_name_plural': 'orders', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='OrderedFood', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('remark', models.TextField(blank=True, default='', verbose_name='remark')), | ||||
|                 ('priority', models.CharField(blank=True, default='', max_length=64, verbose_name='priority request')), | ||||
|                 ('number', models.IntegerField(help_text='How many times the user ordered this.', verbose_name='number')), | ||||
|                 ('status', models.CharField(choices=[('QUEUED', 'queued'), ('READY', 'ready'), ('SERVED', 'served'), ('CANCELED', 'canceled')], max_length=8, verbose_name='status')), | ||||
|                 ('served_date', models.DateTimeField(default=None, null=True, verbose_name='served date')), | ||||
|                 ('food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Food', verbose_name='food')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'ordered food', | ||||
|                 'verbose_name_plural': 'ordered food', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='Sheet', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('name', models.CharField(max_length=255, verbose_name='name')), | ||||
|                 ('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='start date')), | ||||
|                 ('description', models.TextField(verbose_name='description')), | ||||
|                 ('visible', models.BooleanField(default=False, help_text='the note sheet will be private until this field is checked.', verbose_name='visible')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'note sheet', | ||||
|                 'verbose_name_plural': 'note sheets', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='SheetOrderTransaction', | ||||
|             fields=[ | ||||
|                 ('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')), | ||||
|                 ('ordered_food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.OrderedFood', verbose_name='ordered food')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'sheet order transaction', | ||||
|                 'verbose_name_plural': 'sheet order transactions', | ||||
|             }, | ||||
|             bases=('note.transaction',), | ||||
|         ), | ||||
|         migrations.CreateModel( | ||||
|             name='OrderedMeal', | ||||
|             fields=[ | ||||
|                 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||||
|                 ('meal', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Meal', verbose_name='meal')), | ||||
|                 ('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Order', verbose_name='order')), | ||||
|             ], | ||||
|             options={ | ||||
|                 'verbose_name': 'ordered meal', | ||||
|                 'verbose_name_plural': 'ordered meals', | ||||
|             }, | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='orderedfood', | ||||
|             name='meal', | ||||
|             field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sheets.OrderedMeal', verbose_name='ordered meal'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='orderedfood', | ||||
|             name='options', | ||||
|             field=models.ManyToManyField(blank=True, to='sheets.FoodOption', verbose_name='options'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='orderedfood', | ||||
|             name='order', | ||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Order', verbose_name='order'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='order', | ||||
|             name='sheet', | ||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Sheet', verbose_name='note sheet'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='meal', | ||||
|             name='sheet', | ||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Sheet', verbose_name='note sheet'), | ||||
|         ), | ||||
|         migrations.AddField( | ||||
|             model_name='food', | ||||
|             name='sheet', | ||||
|             field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Sheet', verbose_name='note sheet'), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										34
									
								
								apps/sheets/migrations/0002_auto_20220818_1713.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								apps/sheets/migrations/0002_auto_20220818_1713.py
									
									
									
									
									
										Normal file
									
								
							| @@ -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'), | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										0
									
								
								apps/sheets/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/sheets/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										289
									
								
								apps/sheets/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								apps/sheets/models.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,289 @@ | ||||
| # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.db import models | ||||
| from django.urls import reverse_lazy | ||||
| from django.utils import timezone | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
| from member.models import Club | ||||
| from note.models import Note, Transaction | ||||
|  | ||||
|  | ||||
| class Sheet(models.Model): | ||||
|     name = models.CharField( | ||||
|         max_length=255, | ||||
|         verbose_name=_("name"), | ||||
|     ) | ||||
|  | ||||
|     date = models.DateTimeField( | ||||
|         verbose_name=_("start date"), | ||||
|         default=timezone.now, | ||||
|     ) | ||||
|  | ||||
|     description = models.TextField( | ||||
|         verbose_name=_("description"), | ||||
|     ) | ||||
|  | ||||
|     visible = models.BooleanField( | ||||
|         default=False, | ||||
|         verbose_name=_("visible"), | ||||
|         help_text=_("the note sheet will be private until this field is checked."), | ||||
|     ) | ||||
|  | ||||
|     def get_absolute_url(self): | ||||
|         return reverse_lazy('sheets:sheet_detail', args=(self.pk,)) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("note sheet") | ||||
|         verbose_name_plural = _("note sheets") | ||||
|  | ||||
|  | ||||
| class Food(models.Model): | ||||
|     name = models.CharField( | ||||
|         max_length=255, | ||||
|         verbose_name=_("food"), | ||||
|     ) | ||||
|  | ||||
|     sheet = models.ForeignKey( | ||||
|         Sheet, | ||||
|         on_delete=models.CASCADE, | ||||
|         verbose_name=_("note sheet"), | ||||
|     ) | ||||
|  | ||||
|     price = models.IntegerField( | ||||
|         verbose_name=_("price"), | ||||
|     ) | ||||
|  | ||||
|     club = models.ForeignKey( | ||||
|         Club, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("destination club"), | ||||
|     ) | ||||
|  | ||||
|     available = models.BooleanField( | ||||
|         default=True, | ||||
|         verbose_name=_("available"), | ||||
|         help_text=_("If set to false, this option won't be offered (in case of out of stock)"), | ||||
|     ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("food") | ||||
|         verbose_name_plural = _("food") | ||||
|  | ||||
|  | ||||
| class FoodOption(models.Model): | ||||
|     name = models.CharField( | ||||
|         max_length=255, | ||||
|         verbose_name=_("name"), | ||||
|     ) | ||||
|  | ||||
|     food = models.ForeignKey( | ||||
|         Food, | ||||
|         on_delete=models.CASCADE, | ||||
|         verbose_name=_("food"), | ||||
|     ) | ||||
|  | ||||
|     extra_cost = models.IntegerField( | ||||
|         default=0, | ||||
|         verbose_name=_("extra cost"), | ||||
|     ) | ||||
|  | ||||
|     available = models.BooleanField( | ||||
|         default=True, | ||||
|         verbose_name=_("available"), | ||||
|         help_text=_("If set to false, this option won't be offered (in case of out of stock)"), | ||||
|     ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("food option") | ||||
|         verbose_name_plural = _("food options") | ||||
|  | ||||
|  | ||||
| class Meal(models.Model): | ||||
|     sheet = models.ForeignKey( | ||||
|         Sheet, | ||||
|         on_delete=models.CASCADE, | ||||
|         verbose_name=_("note sheet"), | ||||
|     ) | ||||
|  | ||||
|     name = models.CharField( | ||||
|         max_length=255, | ||||
|         verbose_name=_("name"), | ||||
|     ) | ||||
|  | ||||
|     content = models.ManyToManyField( | ||||
|         Food, | ||||
|         verbose_name=_("content"), | ||||
|     ) | ||||
|  | ||||
|     price = models.IntegerField( | ||||
|         verbose_name=_("price"), | ||||
|     ) | ||||
|  | ||||
|     available = models.BooleanField( | ||||
|         default=True, | ||||
|         verbose_name=_("available"), | ||||
|         help_text=_("If set to false, this option won't be offered (in case of out of stock)"), | ||||
|     ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return _("meal").capitalize() + " " + self.name | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("meal") | ||||
|         verbose_name_plural = _("meals") | ||||
|  | ||||
|  | ||||
| class Order(models.Model): | ||||
|     sheet = models.ForeignKey( | ||||
|         Sheet, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("note sheet"), | ||||
|     ) | ||||
|  | ||||
|     note = models.ForeignKey( | ||||
|         Note, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("note"), | ||||
|     ) | ||||
|  | ||||
|     date = models.DateTimeField( | ||||
|         verbose_name=_("date"), | ||||
|         auto_now_add=True, | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("order") | ||||
|         verbose_name_plural = _("orders") | ||||
|  | ||||
|  | ||||
| class OrderedMeal(models.Model): | ||||
|     order = models.ForeignKey( | ||||
|         Order, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("order"), | ||||
|     ) | ||||
|  | ||||
|     meal = models.ForeignKey( | ||||
|         Meal, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("meal"), | ||||
|     ) | ||||
|  | ||||
|     gift = models.IntegerField( | ||||
|         verbose_name=_("gift"), | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("ordered meal") | ||||
|         verbose_name_plural = _("ordered meals") | ||||
|  | ||||
|  | ||||
| class OrderedFood(models.Model): | ||||
|     order = models.ForeignKey( | ||||
|         Order, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("order"), | ||||
|     ) | ||||
|  | ||||
|     meal = models.ForeignKey( | ||||
|         OrderedMeal, | ||||
|         on_delete=models.SET_NULL, | ||||
|         null=True, | ||||
|         default=None, | ||||
|         verbose_name=_("ordered meal"), | ||||
|     ) | ||||
|  | ||||
|     food = models.ForeignKey( | ||||
|         Food, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("food"), | ||||
|     ) | ||||
|  | ||||
|     options = models.ManyToManyField( | ||||
|         FoodOption, | ||||
|         blank=True, | ||||
|         verbose_name=_("options"), | ||||
|     ) | ||||
|  | ||||
|     remark = models.TextField( | ||||
|         blank=True, | ||||
|         default="", | ||||
|         verbose_name=_("remark"), | ||||
|     ) | ||||
|  | ||||
|     priority = models.CharField( | ||||
|         max_length=64, | ||||
|         blank=True, | ||||
|         default="", | ||||
|         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."), | ||||
|     ) | ||||
|  | ||||
|     status = models.CharField( | ||||
|         max_length=8, | ||||
|         choices=[ | ||||
|             ('QUEUED', _("queued")), | ||||
|             ('READY', _("ready")), | ||||
|             ('SERVED', _("served")), | ||||
|             ('CANCELED', _("canceled")), | ||||
|         ], | ||||
|         default='QUEUED', | ||||
|         verbose_name=_("status"), | ||||
|     ) | ||||
|  | ||||
|     served_date = models.DateTimeField( | ||||
|         null=True, | ||||
|         default=None, | ||||
|         verbose_name=_("served date") | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("ordered food") | ||||
|         verbose_name_plural = _("ordered food") | ||||
|  | ||||
|  | ||||
| class SheetOrderTransaction(Transaction): | ||||
|     ordered_food = models.ForeignKey( | ||||
|         OrderedFood, | ||||
|         on_delete=models.PROTECT, | ||||
|         verbose_name=_("ordered food"), | ||||
|     ) | ||||
|  | ||||
|     @property | ||||
|     def type(self): | ||||
|         return _("note sheet") | ||||
|  | ||||
|     @property | ||||
|     def get_price(self): | ||||
|         if self.ordered_food.meal: | ||||
|             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 + self.ordered_food.gift \ | ||||
|                    + sum(opt.extra_cost for opt in self.ordered_food.options.all()) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("sheet order transaction") | ||||
|         verbose_name_plural = _("sheet order transactions") | ||||
							
								
								
									
										22
									
								
								apps/sheets/tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								apps/sheets/tables.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import django_tables2 as tables | ||||
| from django.urls import reverse_lazy | ||||
|  | ||||
| from sheets.models import Sheet | ||||
|  | ||||
|  | ||||
| class SheetTable(tables.Table): | ||||
|     class Meta: | ||||
|         attrs = { | ||||
|             'class': 'table table-condensed table-striped table-hover' | ||||
|         } | ||||
|         model = Sheet | ||||
|         template_name = 'django_tables2/bootstrap4.html' | ||||
|         fields = ('name', 'date', ) | ||||
|         row_attrs = { | ||||
|             'class': 'table-row', | ||||
|             'id': lambda record: "row-" + str(record.pk), | ||||
|             'data-href': lambda record: reverse_lazy('sheets:sheet_detail', args=(record.pk,)) | ||||
|         } | ||||
							
								
								
									
										86
									
								
								apps/sheets/templates/sheets/food_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								apps/sheets/templates/sheets/food_form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| {% extends "base.html" %} | ||||
| {% comment %} | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| {% endcomment %} | ||||
| {% load crispy_forms_tags %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="card bg-light mb-3"> | ||||
|     <h3 class="card-header text-center"> | ||||
|         {{ title }} | ||||
|     </h3> | ||||
|     <div class="card-body"> | ||||
|         <form method="post"> | ||||
|             {% csrf_token %} | ||||
|             {{ form|crispy }} | ||||
|  | ||||
|             {# The next part concerns the option formset #} | ||||
|             {# Generate some hidden fields that manage the number of options, and make easier the parsing #} | ||||
|             {{ formset.management_form }} | ||||
|             <table class="table table-condensed table-striped"> | ||||
|                 {# Fill initial data #} | ||||
|                 {% for form in formset %} | ||||
|                 {% if forloop.first %} | ||||
|                 <thead> | ||||
|                     <tr> | ||||
|                         <th>{{ form.name.label }}<span class="asteriskField">*</span></th> | ||||
|                         <th>{{ form.extra_cost.label }}<span class="asteriskField">*</span></th> | ||||
|                         <th>{{ form.available.label }}<span class="asteriskField">*</span></th> | ||||
|                     </tr> | ||||
|                 </thead> | ||||
|                 <tbody id="form_body"> | ||||
|                     {% endif %} | ||||
|                     <tr class="row-formset"> | ||||
|                         <td>{{ form.name }}</td> | ||||
|                         <td>{{ form.extra_cost }}</td> | ||||
|                         <td>{{ form.available }}</td> | ||||
|                         {# These fields are hidden but handled by the formset to link the id and the invoice id #} | ||||
|                         {{ form.food }} | ||||
|                         {{ form.id }} | ||||
|                     </tr> | ||||
|                     {% endfor %} | ||||
|                 </tbody> | ||||
|             </table> | ||||
|  | ||||
|             {# Display buttons to add and remove options #} | ||||
|             <div class="card-body"> | ||||
|             <button type="button" id="add_more" class="btn btn-success">{% trans "Add option" %}</button> | ||||
|             </div> | ||||
|  | ||||
|             <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button> | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| {# Hidden div that store an empty product form, to be copied into new forms #} | ||||
| <div id="empty_form" style="display: none;"> | ||||
|     <table class='no_error'> | ||||
|         <tbody id="for_real"> | ||||
|             <tr class="row-formset"> | ||||
|                 <td>{{ formset.empty_form.name }}</td> | ||||
|                 <td>{{ formset.empty_form.extra_cost }} </td> | ||||
|                 <td>{{ formset.empty_form.available }}</td> | ||||
|                 {{ formset.empty_form.food }} | ||||
|                 {{ formset.empty_form.id }} | ||||
|             </tr> | ||||
|         </tbody> | ||||
|     </table> | ||||
| </div> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block extrajavascript %} | ||||
| <script> | ||||
|     /* script that handles add and remove lines */ | ||||
|     IDS = {}; | ||||
|  | ||||
|     $("#id_foodoption_set-TOTAL_FORMS").val($(".row-formset").length - 1); | ||||
|  | ||||
|     $('#add_more').click(function () { | ||||
|         let form_idx = $('#id_foodoption_set-TOTAL_FORMS').val(); | ||||
|         $('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx)); | ||||
|         $('#id_foodoption_set-TOTAL_FORMS').val(parseInt(form_idx) + 1); | ||||
|         $('#id_foodoption_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]); | ||||
|     }); | ||||
| </script> | ||||
| {% endblock %} | ||||
							
								
								
									
										21
									
								
								apps/sheets/templates/sheets/meal_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								apps/sheets/templates/sheets/meal_form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| {% extends "base.html" %} | ||||
| {% comment %} | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| {% endcomment %} | ||||
| {% load crispy_forms_tags %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="card bg-light mb-3"> | ||||
|     <h3 class="card-header text-center"> | ||||
|         {{ title }} | ||||
|     </h3> | ||||
|     <div class="card-body"> | ||||
|         <form method="post"> | ||||
|             {% csrf_token %} | ||||
|             {{ form|crispy }} | ||||
|             <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button> | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										87
									
								
								apps/sheets/templates/sheets/sheet_detail.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								apps/sheets/templates/sheets/sheet_detail.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% load i18n %} | ||||
| {% load pretty_money %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="card"> | ||||
| <div class="card-header text-center"> | ||||
|     <h1>{{ sheet.name }}</h1> | ||||
| </div> | ||||
| <div class="card-body"> | ||||
|     <div class="alert alert-secondary"> | ||||
|         <div class="row"> | ||||
|             <div class="col-sm-11"> | ||||
|                 {{ sheet.description }} | ||||
|             </div> | ||||
|             {% if can_change_sheet %} | ||||
|                 <div class="col-sm-1"> | ||||
|                     <a class="badge badge-primary" href="{% url 'sheets:sheet_update' pk=sheet.pk %}"> | ||||
|                         <i class="fa fa-edit"></i> | ||||
|                         {% trans "Edit" %} | ||||
|                     </a> | ||||
|                 </div> | ||||
|             {% endif %} | ||||
|         </div> | ||||
|     </div> | ||||
|  | ||||
|     <h3>{% trans "menu"|capfirst %} :</h3> | ||||
|     <ul> | ||||
|         {% for meal in sheet.meal_set.all %} | ||||
|             <li{% if not meal.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}> | ||||
|                 {{ meal }} ({{ meal.price|pretty_money }}) | ||||
|                 {% if can_change_sheet %} | ||||
|                     <a href="{% url 'sheets:meal_update' pk=meal.pk %}" class="badge badge-primary"> | ||||
|                         <i class="fa fa-edit"></i> | ||||
|                         {% trans "Edit" %} | ||||
|                     </a> | ||||
|                 {% endif %} | ||||
|             </li> | ||||
|         {% endfor %} | ||||
|         <hr> | ||||
|         {% for food in sheet.food_set.all %} | ||||
|             <li{% if not food.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}> | ||||
|                 {{ food }} ({{ food.price|pretty_money }}) | ||||
|                 <a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="badge badge-primary"> | ||||
|                     <i class="fa fa-list"></i> | ||||
|                     {% trans "Waiting list" %} | ||||
|                 </a> | ||||
|                 {% if can_change_sheet %} | ||||
|                     <a href="{% url 'sheets:food_update' pk=food.pk %}" class="badge badge-primary"> | ||||
|                         <i class="fa fa-edit"></i> | ||||
|                         {% trans "Edit" %} | ||||
|                     </a> | ||||
|                 {% endif %} | ||||
|                 {% if food.foodoption_set.all %} | ||||
|                     <ul> | ||||
|                         {% for option in food.foodoption_set.all %} | ||||
|                             <li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}> | ||||
|                                 {{ option }}{% if option.extra_cost %} ({{ option.extra_cost|pretty_money }}){% endif %} | ||||
|                             </li> | ||||
|                         {% endfor %} | ||||
|                     </ul> | ||||
|                 {% endif %} | ||||
|             </li> | ||||
|         {% empty %} | ||||
|             <div class="alert alert-warning"> | ||||
|                 {% trans "The menu is empty for now." %} | ||||
|             </div> | ||||
|         {% endfor %} | ||||
|     </ul> | ||||
|  | ||||
|     <div class="text-center"> | ||||
|         {% if can_add_food %} | ||||
|             <a href="{% url 'sheets:food_create' pk=sheet.pk %}" class="btn btn-primary">{% trans "Add new food" %}</a> | ||||
|         {% endif %} | ||||
|         {% if can_add_meal %} | ||||
|             <a href="{% url 'sheets:meal_create' pk=sheet.pk %}" class="btn btn-primary">{% trans "Add new meal" %}</a> | ||||
|         {% endif %} | ||||
|     </div> | ||||
| </div> | ||||
| <div class="card-footer text-center"> | ||||
|     <a href="{% url 'sheets:sheet_order' pk=sheet.pk %}" class="btn btn-success"> | ||||
|         {% trans "Order now" %} | ||||
|     </a> | ||||
| </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										21
									
								
								apps/sheets/templates/sheets/sheet_form.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								apps/sheets/templates/sheets/sheet_form.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| {% extends "base.html" %} | ||||
| {% comment %} | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| {% endcomment %} | ||||
| {% load crispy_forms_tags %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="card bg-light mb-3"> | ||||
|     <h3 class="card-header text-center"> | ||||
|         {{ title }} | ||||
|     </h3> | ||||
|     <div class="card-body"> | ||||
|         <form method="post"> | ||||
|             {% csrf_token %} | ||||
|             {{ form|crispy }} | ||||
|             <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button> | ||||
|         </form> | ||||
|     </div> | ||||
| </div> | ||||
| {% endblock %} | ||||
							
								
								
									
										74
									
								
								apps/sheets/templates/sheets/sheet_list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								apps/sheets/templates/sheets/sheet_list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| {% extends "base.html" %} | ||||
| {% comment %} | ||||
| SPDX-License-Identifier: GPL-3.0-or-later | ||||
| {% endcomment %} | ||||
| {% load render_table from django_tables2 %} | ||||
| {% load i18n %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="row justify-content-center mb-4"> | ||||
|     <div class="col-md-10 text-center"> | ||||
|         <input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved()" id="search_field"/> | ||||
|         {% if can_create_sheet %} | ||||
|             <hr> | ||||
|             <a class="btn btn-primary text-center my-4" href="{% url 'sheets:sheet_create' %}">{% trans "Create a sheet" %}</a> | ||||
|         {% endif %} | ||||
|     </div> | ||||
| </div> | ||||
| <div class="row justify-content-center">    | ||||
|     <div class="col-md-10"> | ||||
|         <div class="card card-border shadow"> | ||||
|             <div class="card-header text-center"> | ||||
|                 <h5> {% trans "Note sheet listing" %}</h5> | ||||
|             </div> | ||||
|             <div class="card-body px-0 py-0" id="sheets_table"> | ||||
|                 {% render_table table %} | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
|  | ||||
| {% endblock %} | ||||
| {% block extrajavascript %} | ||||
| <script type="text/javascript"> | ||||
|  | ||||
| function getInfo() { | ||||
|     var asked = $("#search_field").val(); | ||||
|     /* on ne fait la requête que si on a au moins un caractère pour chercher */ | ||||
|     var sel = $(".table-row"); | ||||
|     if (asked.length >= 1) { | ||||
|         $.getJSON("/api/sheets/sheet/?format=json&search="+asked, function(buttons){ | ||||
|             let selected_id = buttons.results.map((a => "#row-"+a.id)); | ||||
|             if (selected_id.length) | ||||
|                 $(".table-row,"+selected_id.join()).show(); | ||||
|             $(".table-row").not(selected_id.join()).hide(); | ||||
|              | ||||
|         }); | ||||
|     }else{ | ||||
|         // show everything | ||||
|         $('table tr').show(); | ||||
|     }        | ||||
| } | ||||
| var timer; | ||||
| var timer_on; | ||||
| /* Fontion appelée quand le texte change (délenche le timer) */ | ||||
| function search_field_moved(secondfield) { | ||||
|     if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur. | ||||
|         clearTimeout(timer); | ||||
|         timer = setTimeout("getInfo(" + secondfield + ")", 300); | ||||
|     } | ||||
|     else { // Sinon, on le lance et on enregistre le fait qu'il tourne. | ||||
|         timer = setTimeout("getInfo(" + secondfield + ")", 300); | ||||
|         timer_on = true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| // clickable row  | ||||
| $(document).ready(function($) { | ||||
|     $(".table-row").click(function() { | ||||
|         window.document.location = $(this).data("href"); | ||||
|     }); | ||||
| }); | ||||
|  | ||||
| </script> | ||||
| {% endblock %} | ||||
							
								
								
									
										0
									
								
								apps/sheets/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/sheets/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										26
									
								
								apps/sheets/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								apps/sheets/urls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
|  | ||||
| from sheets.views import FoodCreateView, FoodUpdateView, MealCreateView, MealUpdateView, OrderView, \ | ||||
|     SheetCreateView, SheetDetailView, SheetListView, SheetUpdateView, WaitingListDetailView, WaitingListView | ||||
|  | ||||
| app_name = 'sheets' | ||||
|  | ||||
| urlpatterns = [ | ||||
|     path('list/', SheetListView.as_view(), name="sheet_list"), | ||||
|     path('create/', SheetCreateView.as_view(), name="sheet_create"), | ||||
|     path('update/<int:pk>/', SheetUpdateView.as_view(), name="sheet_update"), | ||||
|     path('detail/<int:pk>/', SheetDetailView.as_view(), name="sheet_detail"), | ||||
|     path('food/create/<int:pk>/', FoodCreateView.as_view(), name="food_create"), | ||||
|     path('food/<int:pk>/update/', FoodUpdateView.as_view(), name="food_update"), | ||||
|     path('meal/create/<int:pk>/', MealCreateView.as_view(), name="meal_create"), | ||||
|     path('meal/<int:pk>/update/', MealUpdateView.as_view(), name="meal_update"), | ||||
|     path('order/<int:pk>/', OrderView.as_view(), name="sheet_order"), | ||||
|     path('waiting-list/<int:pk>/', WaitingListView.as_view(), name="waiting_list"), | ||||
|     path('waiting-list/<int:pk>/queued/', WaitingListDetailView.as_view(), name="queued_list"), | ||||
|     path('waiting-list/<int:pk>/ready/', WaitingListDetailView.as_view(), name="ready_list"), | ||||
|     path('waiting-list/<int:pk>/served/', WaitingListDetailView.as_view(), name="served_list"), | ||||
|     path('waiting-list/<int:pk>/canceled/', WaitingListDetailView.as_view(), name="canceled_list"), | ||||
| ] | ||||
							
								
								
									
										444
									
								
								apps/sheets/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										444
									
								
								apps/sheets/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,444 @@ | ||||
| # 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, 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, Order, OrderedMeal, OrderedFood, SheetOrderTransaction | ||||
| from .tables import SheetTable | ||||
|  | ||||
|  | ||||
| class SheetListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): | ||||
|     model = Sheet | ||||
|     table_class = SheetTable | ||||
|     ordering = '-date' | ||||
|     extra_context = {"title": _("Search note sheet")} | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         context["can_create_sheet"] = PermissionBackend.check_perm(self.request, "sheets.add_sheet", Sheet( | ||||
|             name="Test", | ||||
|             date=timezone.now(), | ||||
|             description="Test sheet", | ||||
|         )) | ||||
|         return context | ||||
|  | ||||
|  | ||||
| class SheetCreateView(ProtectQuerysetMixin, ProtectedCreateView): | ||||
|     model = Sheet | ||||
|     form_class = SheetForm | ||||
|     extra_context = {"title": _("Create note sheet")} | ||||
|  | ||||
|     def get_sample_object(self): | ||||
|         return Sheet( | ||||
|             name="Test", | ||||
|             date=timezone.now(), | ||||
|             description="Test", | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SheetUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): | ||||
|     model = Sheet | ||||
|     form_class = SheetForm | ||||
|     extra_context = {"title": _("Update note sheet")} | ||||
|  | ||||
|  | ||||
| class SheetDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): | ||||
|     model = Sheet | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data() | ||||
|  | ||||
|         context['can_change_sheet'] = PermissionBackend.check_perm(self.request, 'sheets.change_sheet', self.object) | ||||
|         context['can_add_meal'] = PermissionBackend.check_perm(self.request, | ||||
|                                                                'sheets.add_meal', | ||||
|                                                                Meal(sheet=self.object, name="Test", price=500)) | ||||
|         context['can_add_food'] = PermissionBackend.check_perm(self.request, | ||||
|                                                                'sheets.add_food', | ||||
|                                                                Food(sheet=self.object, name="Test", price=500)) | ||||
|  | ||||
|         return context | ||||
|  | ||||
|  | ||||
| class FoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): | ||||
|     model = Food | ||||
|     form_class = FoodForm | ||||
|     extra_context = {"title": _("Create new food")} | ||||
|  | ||||
|     def get_sample_object(self): | ||||
|         return Food( | ||||
|             sheet_id=self.kwargs['pk'], | ||||
|             name="Test", | ||||
|             price=500, | ||||
|         ) | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|  | ||||
|         form = context['form'] | ||||
|         form.helper = FormHelper() | ||||
|         # Remove form tag on the generation of the form in the template (already present on the template) | ||||
|         form.helper.form_tag = False | ||||
|         # The formset handles the set of the products | ||||
|         form_set = FoodOptionsFormSet(instance=form.instance) | ||||
|         context['formset'] = form_set | ||||
|         context['helper'] = FoodOptionFormSetHelper() | ||||
|  | ||||
|         return context | ||||
|  | ||||
|     def form_valid(self, form): | ||||
|         form.instance.sheet_id = self.kwargs['pk'] | ||||
|  | ||||
|         # For each product, we save it | ||||
|         formset = FoodOptionsFormSet(self.request.POST, instance=form.instance) | ||||
|         if formset.is_valid(): | ||||
|             for f in formset: | ||||
|                 # We don't save the product if the designation is not entered, ie. if the line is empty | ||||
|                 if f.is_valid() and f.instance.name: | ||||
|                     f.save() | ||||
|                     f.instance.save() | ||||
|                 else: | ||||
|                     f.instance = None | ||||
|  | ||||
|         return super().form_valid(form) | ||||
|  | ||||
|     def get_success_url(self): | ||||
|         return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],)) | ||||
|  | ||||
|  | ||||
| class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): | ||||
|     model = Food | ||||
|     form_class = FoodForm | ||||
|     extra_context = {"title": _("Update food")} | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|  | ||||
|         form = context['form'] | ||||
|         form.helper = FormHelper() | ||||
|         # Remove form tag on the generation of the form in the template (already present on the template) | ||||
|         form.helper.form_tag = False | ||||
|         # The formset handles the set of the products | ||||
|         form_set = FoodOptionsFormSet(instance=form.instance) | ||||
|         context['formset'] = form_set | ||||
|         context['helper'] = FoodOptionFormSetHelper() | ||||
|  | ||||
|         return context | ||||
|  | ||||
|     def form_valid(self, form): | ||||
|         # For each product, we save it | ||||
|         formset = FoodOptionsFormSet(self.request.POST, instance=form.instance) | ||||
|         if formset.is_valid(): | ||||
|             for f in formset: | ||||
|                 # We don't save the product if the designation is not entered, ie. if the line is empty | ||||
|                 if f.is_valid() and f.instance.name: | ||||
|                     f.save() | ||||
|                     f.instance.save() | ||||
|                 else: | ||||
|                     f.instance = None | ||||
|  | ||||
|         return super().form_valid(form) | ||||
|  | ||||
|     def get_success_url(self): | ||||
|         return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,)) | ||||
|  | ||||
|  | ||||
| class MealCreateView(ProtectQuerysetMixin, ProtectedCreateView): | ||||
|     model = Meal | ||||
|     form_class = MealForm | ||||
|     extra_context = {"title": _("Create new meal")} | ||||
|  | ||||
|     def get_sample_object(self): | ||||
|         return Meal( | ||||
|             sheet_id=self.kwargs['pk'], | ||||
|             name="Test", | ||||
|             price=500, | ||||
|         ) | ||||
|  | ||||
|     def get_form(self, form_class=None): | ||||
|         form = super().get_form(form_class) | ||||
|         form.fields['content'].queryset = form.fields['content'].queryset.filter(sheet_id=self.kwargs['pk']) | ||||
|         return form | ||||
|  | ||||
|     def form_valid(self, form): | ||||
|         form.instance.sheet_id = self.kwargs['pk'] | ||||
|         return super().form_valid(form) | ||||
|  | ||||
|     def get_success_url(self): | ||||
|         return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,)) | ||||
|  | ||||
|  | ||||
| class MealUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): | ||||
|     model = Meal | ||||
|     form_class = MealForm | ||||
|     extra_context = {"title": _("Update meal")} | ||||
|  | ||||
|     def get_form(self, form_class=None): | ||||
|         form = super().get_form(form_class) | ||||
|         form.fields['content'].queryset = form.fields['content'].queryset.filter(sheet=self.object.sheet) | ||||
|         return form | ||||
|  | ||||
|     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(), | ||||
|                 help_text=_("Be careful: this gift will be multiplied for each order."), | ||||
|             ) | ||||
|             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(), | ||||
|                 help_text=_("Be careful: this gift will be multiplied for each order."), | ||||
|             ) | ||||
|             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'],)) | ||||
|  | ||||
|  | ||||
| class WaitingListView(ProtectQuerysetMixin, DetailView): | ||||
|     model = Food | ||||
|     template_name = 'sheets/waiting_list.html' | ||||
|     extra_context = {'title': _("Waiting list")} | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         content = super().get_context_data(**kwargs) | ||||
|  | ||||
|         content['queue'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status='QUEUED')\ | ||||
|             .order_by('-priority', 'number', 'order__date').all() | ||||
|         content['ready'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status='READY')\ | ||||
|             .order_by('served_date').all() | ||||
|  | ||||
|         return content | ||||
|  | ||||
|  | ||||
| class WaitingListDetailView(ProtectQuerysetMixin, DetailView): | ||||
|     model = Food | ||||
|     template_name = 'sheets/waiting_list_detail.html' | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         context = super().get_context_data(**kwargs) | ||||
|  | ||||
|         list_type = 'CANCELED' if 'canceled' in self.request.path else \ | ||||
|             'SERVED' if 'served' in self.request.path else \ | ||||
|             'READY' if 'ready' in self.request.path else 'QUEUED' | ||||
|         context['list_type'] = list_type | ||||
|         context['orders'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status=list_type)\ | ||||
|             .order_by('served_date', '-priority', 'number', 'order__date').all() | ||||
|         context['title'] = self.object.name + " - " + _(list_type.lower()).capitalize() | ||||
|  | ||||
|         return context | ||||
| @@ -7,7 +7,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: \n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2022-04-10 22:34+0200\n" | ||||
| "POT-Creation-Date: 2022-08-18 22:51+0200\n" | ||||
| "PO-Revision-Date: 2022-04-11 22:05+0200\n" | ||||
| "Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n" | ||||
| "Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n" | ||||
| @@ -60,6 +60,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." | ||||
| #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:301 | ||||
| #: apps/permission/models.py:330 | ||||
| #: apps/registration/templates/registration/future_profile_detail.html:16 | ||||
| #: apps/sheets/models.py:16 apps/sheets/models.py:84 apps/sheets/models.py:121 | ||||
| #: apps/wei/models.py:67 apps/wei/models.py:131 apps/wei/tables.py:282 | ||||
| #: apps/wei/templates/wei/base.html:26 | ||||
| #: apps/wei/templates/wei/weimembership_form.html:14 | ||||
| @@ -95,7 +96,8 @@ msgstr "types d'activité" | ||||
| #: apps/activity/models.py:68 | ||||
| #: apps/activity/templates/activity/includes/activity_info.html:19 | ||||
| #: apps/note/models/transactions.py:81 apps/permission/models.py:110 | ||||
| #: apps/permission/models.py:189 apps/wei/models.py:78 apps/wei/models.py:142 | ||||
| #: apps/permission/models.py:189 apps/sheets/models.py:25 apps/wei/models.py:78 | ||||
| #: apps/wei/models.py:142 | ||||
| msgid "description" | ||||
| msgstr "description" | ||||
|  | ||||
| @@ -143,6 +145,7 @@ msgstr "" | ||||
|  | ||||
| #: apps/activity/models.py:109 | ||||
| #: apps/activity/templates/activity/includes/activity_info.html:25 | ||||
| #: apps/sheets/models.py:20 | ||||
| msgid "start date" | ||||
| msgstr "date de début" | ||||
|  | ||||
| @@ -171,7 +174,7 @@ msgid "entry time" | ||||
| msgstr "heure d'entrée" | ||||
|  | ||||
| #: apps/activity/models.py:178 apps/note/apps.py:14 | ||||
| #: apps/note/models/notes.py:77 | ||||
| #: apps/note/models/notes.py:77 apps/sheets/models.py:157 | ||||
| msgid "note" | ||||
| msgstr "note" | ||||
|  | ||||
| @@ -334,7 +337,10 @@ msgstr "Entrée effectuée !" | ||||
| #: apps/member/templates/member/add_members.html:46 | ||||
| #: apps/member/templates/member/club_form.html:16 | ||||
| #: apps/note/templates/note/transactiontemplate_form.html:18 | ||||
| #: apps/treasury/forms.py:89 apps/treasury/forms.py:143 | ||||
| #: apps/sheets/templates/sheets/food_form.html:51 | ||||
| #: apps/sheets/templates/sheets/meal_form.html:17 | ||||
| #: apps/sheets/templates/sheets/sheet_form.html:17 apps/treasury/forms.py:89 | ||||
| #: apps/treasury/forms.py:143 | ||||
| #: apps/treasury/templates/treasury/invoice_form.html:74 | ||||
| #: apps/wei/templates/wei/bus_form.html:17 | ||||
| #: apps/wei/templates/wei/busteam_form.html:17 | ||||
| @@ -1470,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:277 | ||||
| msgid "quantity" | ||||
| msgstr "quantité" | ||||
|  | ||||
| @@ -1592,8 +1598,10 @@ msgid "Delete" | ||||
| msgstr "Supprimer" | ||||
|  | ||||
| #: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132 | ||||
| #: apps/wei/tables.py:49 apps/wei/tables.py:50 | ||||
| #: apps/wei/templates/wei/base.html:89 | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:21 | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:36 | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:52 apps/wei/tables.py:49 | ||||
| #: apps/wei/tables.py:50 apps/wei/templates/wei/base.html:89 | ||||
| #: apps/wei/templates/wei/bus_detail.html:20 | ||||
| #: apps/wei/templates/wei/busteam_detail.html:20 | ||||
| #: apps/wei/templates/wei/busteam_detail.html:40 | ||||
| @@ -1746,6 +1754,98 @@ msgstr "Bouton affiché" | ||||
| msgid "An error occured" | ||||
| msgstr "Une erreur s'est produite" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:14 apps/sheets/models.py:244 | ||||
| msgid "queued" | ||||
| msgstr "en attente" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:27 | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:67 | ||||
| msgid "There is no queued order." | ||||
| msgstr "Il n'y a pas de commande en attente." | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:35 apps/sheets/models.py:245 | ||||
| msgid "ready" | ||||
| msgstr "prêt" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:43 | ||||
| msgid "There is no ready order." | ||||
| msgstr "Il n'y a pas de commande prête." | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:53 | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:75 | ||||
| msgid "Other waiting lists:" | ||||
| msgstr "Autres listes d'attente :" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:66 | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:93 | ||||
| msgid "Queued orders" | ||||
| msgstr "Commandes en attente" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:69 | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:98 | ||||
| msgid "Ready orders" | ||||
| msgstr "Commandes prêtes" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list.html:72 | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:115 | ||||
| msgid "Back to note sheet detail" | ||||
| msgstr "Retour aux détails de la feuille de note" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:18 | ||||
| #: apps/sheets/models.py:161 | ||||
| msgid "date" | ||||
| msgstr "date" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:22 | ||||
| msgid "order number" | ||||
| msgstr "nombre de commandes" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:27 | ||||
| #: apps/sheets/models.py:229 apps/sheets/views.py:249 apps/sheets/views.py:295 | ||||
| msgid "priority request" | ||||
| msgstr "demande de priorité" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:32 | ||||
| #: apps/sheets/models.py:222 apps/sheets/views.py:243 apps/sheets/views.py:289 | ||||
| msgid "remark" | ||||
| msgstr "remarques" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:37 | ||||
| #: apps/sheets/models.py:216 | ||||
| msgid "options" | ||||
| msgstr "options" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:45 | ||||
| msgid "Mark as ready" | ||||
| msgstr "Marquer comme prêt" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:50 | ||||
| msgid "Mark as served" | ||||
| msgstr "Marquer comme servi" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:55 | ||||
| msgid "Re-queue" | ||||
| msgstr "Remettre en attente" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:60 | ||||
| #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 | ||||
| #: note_kfet/templates/oauth2_provider/authorize.html:28 | ||||
| msgid "Cancel" | ||||
| msgstr "Annuler" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:103 | ||||
| msgid "Served orders" | ||||
| msgstr "Commandes servies" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:108 | ||||
| msgid "Canceled orders" | ||||
| msgstr "Commandes annulées" | ||||
|  | ||||
| #: apps/note/templates/sheets/waiting_list_detail.html:112 | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:47 apps/sheets/views.py:416 | ||||
| msgid "Waiting list" | ||||
| msgstr "Liste d'attente" | ||||
|  | ||||
| #: apps/note/views.py:36 | ||||
| msgid "Transfer money" | ||||
| msgstr "Transférer de l'argent" | ||||
| @@ -2167,6 +2267,237 @@ msgstr "" | ||||
| msgid "Invalidate pre-registration" | ||||
| msgstr "Invalider l'inscription" | ||||
|  | ||||
| #: apps/sheets/apps.py:10 apps/sheets/models.py:42 | ||||
| msgid "note sheets" | ||||
| msgstr "feuilles de notes" | ||||
|  | ||||
| #: apps/sheets/models.py:30 | ||||
| msgid "visible" | ||||
| msgstr "visible" | ||||
|  | ||||
| #: apps/sheets/models.py:31 | ||||
| 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:273 | ||||
| msgid "note sheet" | ||||
| msgstr "feuille de note" | ||||
|  | ||||
| #: apps/sheets/models.py:48 apps/sheets/models.py:77 apps/sheets/models.py:78 | ||||
| #: apps/sheets/models.py:90 apps/sheets/models.py:210 | ||||
| msgid "food" | ||||
| msgstr "nourriture" | ||||
|  | ||||
| #: apps/sheets/models.py:58 apps/sheets/models.py:130 | ||||
| msgid "price" | ||||
| msgstr "prix" | ||||
|  | ||||
| #: apps/sheets/models.py:64 | ||||
| msgid "destination club" | ||||
| msgstr "club de destination" | ||||
|  | ||||
| #: apps/sheets/models.py:69 apps/sheets/models.py:100 apps/sheets/models.py:135 | ||||
| msgid "available" | ||||
| msgstr "disponible" | ||||
|  | ||||
| #: apps/sheets/models.py:70 apps/sheets/models.py:101 apps/sheets/models.py:136 | ||||
| msgid "If set to false, this option won't be offered (in case of out of stock)" | ||||
| msgstr "" | ||||
| "Si mis à faux, cette option ne sera pas présentée (en cas de rupture de " | ||||
| "stock)" | ||||
|  | ||||
| #: apps/sheets/models.py:95 | ||||
| msgid "extra cost" | ||||
| msgstr "surcoût" | ||||
|  | ||||
| #: apps/sheets/models.py:108 | ||||
| msgid "food option" | ||||
| msgstr "option de nourriture" | ||||
|  | ||||
| #: apps/sheets/models.py:109 | ||||
| msgid "food options" | ||||
| msgstr "options de nourriture" | ||||
|  | ||||
| #: apps/sheets/models.py:126 | ||||
| msgid "content" | ||||
| msgstr "contenu" | ||||
|  | ||||
| #: apps/sheets/models.py:140 apps/sheets/models.py:143 | ||||
| #: apps/sheets/models.py:180 | ||||
| msgid "meal" | ||||
| msgstr "menu" | ||||
|  | ||||
| #: apps/sheets/models.py:144 | ||||
| msgid "meals" | ||||
| msgstr "menus" | ||||
|  | ||||
| #: apps/sheets/models.py:166 apps/sheets/models.py:174 | ||||
| #: apps/sheets/models.py:196 | ||||
| msgid "order" | ||||
| msgstr "commande" | ||||
|  | ||||
| #: 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:281 | ||||
| msgid "gift" | ||||
| msgstr "don" | ||||
|  | ||||
| #: apps/sheets/models.py:188 apps/sheets/models.py:204 | ||||
| msgid "ordered meal" | ||||
| msgstr "menu commandé" | ||||
|  | ||||
| #: apps/sheets/models.py:189 | ||||
| msgid "ordered meals" | ||||
| msgstr "menus commandés" | ||||
|  | ||||
| #: apps/sheets/models.py:237 | ||||
| msgid "number" | ||||
| msgstr "numéro" | ||||
|  | ||||
| #: 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:246 | ||||
| msgid "served" | ||||
| msgstr "servi" | ||||
|  | ||||
| #: apps/sheets/models.py:247 | ||||
| msgid "canceled" | ||||
| msgstr "annulé" | ||||
|  | ||||
| #: apps/sheets/models.py:250 | ||||
| msgid "status" | ||||
| msgstr "statut" | ||||
|  | ||||
| #: apps/sheets/models.py:256 | ||||
| msgid "served date" | ||||
| msgstr "date de service" | ||||
|  | ||||
| #: 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:288 | ||||
| msgid "sheet order transaction" | ||||
| msgstr "transaction de commande sur feuille de note" | ||||
|  | ||||
| #: apps/sheets/models.py:289 | ||||
| msgid "sheet order transactions" | ||||
| msgstr "transactions de commande sur feuille de note" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/food_form.html:48 | ||||
| msgid "Add option" | ||||
| msgstr "Ajouter une option" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:28 | ||||
| msgid "menu" | ||||
| msgstr "menu" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:31 | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:43 | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:58 | ||||
| msgid "This product is unavailable." | ||||
| msgstr "Ce produit est indisponible." | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:67 | ||||
| msgid "The menu is empty for now." | ||||
| msgstr "Le menu est vide pour le moment." | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:74 | ||||
| msgid "Add new food" | ||||
| msgstr "Ajouter un plat" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:77 | ||||
| msgid "Add new meal" | ||||
| msgstr "Ajouter un menu" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_detail.html:83 apps/sheets/views.py:206 | ||||
| #: apps/sheets/views.py:319 | ||||
| msgid "Order now" | ||||
| msgstr "Commander maintenant" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_list.html:14 | ||||
| msgid "Create a sheet" | ||||
| msgstr "Créer une feuille de note" | ||||
|  | ||||
| #: apps/sheets/templates/sheets/sheet_list.html:22 | ||||
| msgid "Note sheet listing" | ||||
| msgstr "Liste des feuilles de notes" | ||||
|  | ||||
| #: apps/sheets/views.py:33 | ||||
| msgid "Search note sheet" | ||||
| msgstr "Chercher une feuille de note" | ||||
|  | ||||
| #: apps/sheets/views.py:48 | ||||
| msgid "Create note sheet" | ||||
| msgstr "Créer une feuille de note" | ||||
|  | ||||
| #: apps/sheets/views.py:61 | ||||
| msgid "Update note sheet" | ||||
| msgstr "Modifier une feuille de note" | ||||
|  | ||||
| #: apps/sheets/views.py:84 | ||||
| msgid "Create new food" | ||||
| msgstr "Créer un plat" | ||||
|  | ||||
| #: apps/sheets/views.py:130 | ||||
| msgid "Update food" | ||||
| msgstr "Modifier un plat" | ||||
|  | ||||
| #: apps/sheets/views.py:167 | ||||
| msgid "Create new meal" | ||||
| msgstr "Créer un menu" | ||||
|  | ||||
| #: apps/sheets/views.py:192 | ||||
| msgid "Update meal" | ||||
| msgstr "Modifier un menu" | ||||
|  | ||||
| #: apps/sheets/views.py:217 | ||||
| msgid "Orderer" | ||||
| msgstr "Commanditaire" | ||||
|  | ||||
| #: apps/sheets/views.py:223 | ||||
| msgid "Who orders" | ||||
| msgstr "Qui commande" | ||||
|  | ||||
| #: apps/sheets/views.py:231 apps/treasury/models.py:140 | ||||
| msgid "Quantity" | ||||
| msgstr "Quantité" | ||||
|  | ||||
| #: apps/sheets/views.py:238 apps/sheets/views.py:284 | ||||
| msgid "Be careful: this gift will be multiplied for each order." | ||||
| msgstr "Attention : ce don sera multiplié pour chaque commande." | ||||
|  | ||||
| #: apps/sheets/views.py:244 apps/sheets/views.py:290 | ||||
| msgid "Allergies,…" | ||||
| msgstr "Allergies,…" | ||||
|  | ||||
| #: apps/sheets/views.py:250 apps/sheets/views.py:296 | ||||
| msgid "Lesson at 13h30,…" | ||||
| msgstr "Cours à 13h30,…" | ||||
|  | ||||
| #: apps/sheets/views.py:261 | ||||
| msgid "Options for " | ||||
| msgstr "Options pour " | ||||
|  | ||||
| #: apps/sheets/views.py:306 | ||||
| msgid "Options" | ||||
| msgstr "Options" | ||||
|  | ||||
| #: apps/sheets/views.py:403 | ||||
| msgid "You didn't select anything." | ||||
| msgstr "Vous n'avez rien sélectionné." | ||||
|  | ||||
| #: apps/sheets/views.py:432 | ||||
| msgid "Detailed waiting list" | ||||
| msgstr "Liste d'attente détaillée" | ||||
|  | ||||
| #: apps/treasury/apps.py:12 note_kfet/templates/base.html:96 | ||||
| msgid "Treasury" | ||||
| msgstr "Trésorerie" | ||||
| @@ -2253,10 +2584,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" | ||||
| @@ -3185,19 +3512,19 @@ msgstr "Répartir les 1A dans les bus" | ||||
| msgid "Attribute bus" | ||||
| msgstr "Attribuer un bus" | ||||
|  | ||||
| #: note_kfet/settings/base.py:172 | ||||
| #: note_kfet/settings/base.py:173 | ||||
| msgid "German" | ||||
| msgstr "Allemand" | ||||
|  | ||||
| #: note_kfet/settings/base.py:173 | ||||
| #: note_kfet/settings/base.py:174 | ||||
| msgid "English" | ||||
| msgstr "Anglais" | ||||
|  | ||||
| #: note_kfet/settings/base.py:174 | ||||
| #: note_kfet/settings/base.py:175 | ||||
| msgid "Spanish" | ||||
| msgstr "Espagnol" | ||||
|  | ||||
| #: note_kfet/settings/base.py:175 | ||||
| #: note_kfet/settings/base.py:176 | ||||
| msgid "French" | ||||
| msgstr "Français" | ||||
|  | ||||
| @@ -3347,11 +3674,6 @@ msgstr "Il n'y a pas de résultat." | ||||
| msgid "Are you sure to delete the application" | ||||
| msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application" | ||||
|  | ||||
| #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 | ||||
| #: note_kfet/templates/oauth2_provider/authorize.html:28 | ||||
| msgid "Cancel" | ||||
| msgstr "Annuler" | ||||
|  | ||||
| #: note_kfet/templates/oauth2_provider/application_detail.html:11 | ||||
| msgid "Client id" | ||||
| msgstr "ID client" | ||||
| @@ -3562,3 +3884,6 @@ msgstr "" | ||||
| "vous connecter. Vous devez vous rendre à la Kfet et payer les frais " | ||||
| "d'adhésion. Vous devez également valider votre adresse email en suivant le " | ||||
| "lien que vous avez reçu." | ||||
|  | ||||
| #~ msgid "Detailed view" | ||||
| #~ msgstr "Vue détaillée" | ||||
|   | ||||
| @@ -75,6 +75,7 @@ INSTALLED_APPS = [ | ||||
|     'permission', | ||||
|     'registration', | ||||
|     'scripts', | ||||
|     'sheets', | ||||
|     'treasury', | ||||
|     'wei', | ||||
| ] | ||||
|   | ||||
| @@ -97,13 +97,16 @@ body { | ||||
|  | ||||
| .btn-primary:hover, | ||||
| .btn-primary:not(:disabled):not(.disabled).active, | ||||
| .btn-primary:not(:disabled):not(.disabled):active { | ||||
| .btn-primary:not(:disabled):not(.disabled):active, | ||||
| a.badge-primary:hover, | ||||
| a.badge-primary:not(:disabled):not(.disabled).active, | ||||
| a.badge-primary:not(:disabled):not(.disabled):active { | ||||
|     color: #fff; | ||||
|     background-color: rgb(102, 83, 105); | ||||
|     border-color: rgb(102, 83, 105); | ||||
| } | ||||
|  | ||||
| .btn-primary { | ||||
| .btn-primary, a.badge-primary { | ||||
|     color: rgba(248, 249, 250, 0.9);  | ||||
|     background-color: rgb(102, 83, 105); | ||||
|     border-color: rgb(102, 83, 105); | ||||
|   | ||||
| @@ -21,6 +21,7 @@ urlpatterns = [ | ||||
|     path('activity/', include('activity.urls')), | ||||
|     path('treasury/', include('treasury.urls')), | ||||
|     path('wei/', include('wei.urls')), | ||||
|     path('sheets/', include('sheets.urls')), | ||||
|  | ||||
|     # Include Django Contrib and Core routers | ||||
|     path('i18n/', include('django.conf.urls.i18n')), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user