mirror of https://gitlab.crans.org/bde/nk20
409 lines
17 KiB
Python
409 lines
17 KiB
Python
# 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(),
|
|
)
|
|
form.fields[f'meal_{meal.id}_remark'] = forms.CharField(
|
|
max_length=255,
|
|
required=False,
|
|
label=_("remark").capitalize(),
|
|
help_text=_("Allergies,…"),
|
|
)
|
|
form.fields[f'meal_{meal.id}_priority'] = forms.CharField(
|
|
max_length=64,
|
|
required=False,
|
|
label=_("priority request").capitalize(),
|
|
help_text=_("Lesson at 13h30,…"),
|
|
)
|
|
|
|
ag = AccordionGroup(f"{meal} ({pretty_money(meal.price)})",
|
|
Row(Field(f'meal_{meal.id}_quantity', wrapper_class='col-sm-9'),
|
|
Field(f'meal_{meal.id}_gift', wrapper_class='col-sm-3')),
|
|
Row(Field(f'meal_{meal.id}_remark', wrapper_class='col-sm-9'),
|
|
Field(f'meal_{meal.id}_priority', wrapper_class='col-sm-3')))
|
|
|
|
for food in meal.content.filter(available=True).all():
|
|
if food.foodoption_set.count():
|
|
options_fieldset = Fieldset(_("Options for ") + str(food))
|
|
options_row = Row(css_class='ml-0')
|
|
for option in food.foodoption_set.filter(available=True).all():
|
|
form.fields[f'meal_{meal.id}_food_{food.id}_option_{option.id}'] = forms.BooleanField(
|
|
label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}",
|
|
required=False,
|
|
)
|
|
options_row.fields.append(
|
|
Field(f'meal_{meal.id}_food_{food.id}_option_{option.id}', wrapper_class='col-sm-12'))
|
|
options_fieldset.fields.append(options_row)
|
|
ag.fields.append(options_fieldset)
|
|
|
|
layout_fields.append(ag)
|
|
|
|
for food in self.object.food_set.filter(available=True).all():
|
|
form.fields[f'food_{food.id}_quantity'] = forms.IntegerField(
|
|
label=_("quantity").capitalize(),
|
|
initial=0,
|
|
)
|
|
form.fields[f'food_{food.id}_gift'] = forms.IntegerField(
|
|
label=_("gift").capitalize(),
|
|
initial=0,
|
|
widget=AmountInput(),
|
|
)
|
|
form.fields[f'food_{food.id}_remark'] = forms.CharField(
|
|
max_length=255,
|
|
required=False,
|
|
label=_("remark").capitalize(),
|
|
help_text=_("Allergies,…"),
|
|
)
|
|
form.fields[f'food_{food.id}_priority'] = forms.CharField(
|
|
max_length=255,
|
|
required=False,
|
|
label=_("priority request").capitalize(),
|
|
help_text=_("Lesson at 13h30,…"),
|
|
)
|
|
|
|
ag = AccordionGroup(f"{food} ({pretty_money(food.price)})",
|
|
Row(Field(f'food_{food.id}_quantity', wrapper_class='col-sm-9'),
|
|
Field(f'food_{food.id}_gift', wrapper_class='col-sm-3')),
|
|
Row(Field(f'food_{food.id}_remark', wrapper_class='col-sm-9'),
|
|
Field(f'food_{food.id}_priority', wrapper_class='col-sm-3')))
|
|
|
|
if food.foodoption_set.count():
|
|
options_fieldset = Fieldset(_("Options"))
|
|
options_row = Row(css_class='ml-0')
|
|
for option in food.foodoption_set.all():
|
|
form.fields[f'food_{food.id}_option_{option.id}'] = forms.BooleanField(
|
|
label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}",
|
|
required=False,
|
|
)
|
|
options_row.fields.append(Field(f'food_{food.id}_option_{option.id}', wrapper_class='col-sm-12'))
|
|
options_fieldset.fields.append(options_row)
|
|
ag.fields.append(options_fieldset)
|
|
|
|
layout_fields.append(ag)
|
|
|
|
layout_fields.append(FormActions(Submit('submit', _("Order now"))))
|
|
|
|
form.helper.layout = Accordion(*layout_fields)
|
|
|
|
if self.request.method in ['PUT', 'POST']:
|
|
form.data = self.request.POST
|
|
form.files = self.request.FILES
|
|
form.is_bound = not form.data or not form.files
|
|
|
|
return form
|
|
|
|
def form_valid(self, form):
|
|
data = form.cleaned_data
|
|
sheet = self.get_object()
|
|
|
|
with transaction.atomic():
|
|
order = Order.objects.create(sheet_id=self.kwargs['pk'], note=data['note'])
|
|
|
|
total_quantity = 0
|
|
|
|
for meal in sheet.meal_set.filter(available=True).all():
|
|
quantity = data[f'meal_{meal.id}_quantity']
|
|
if not quantity:
|
|
continue
|
|
|
|
total_quantity += quantity
|
|
gift = data[f'meal_{meal.id}_gift']
|
|
remark = data[f'meal_{meal.id}_remark'] or ''
|
|
priority = data[f'meal_{meal.id}_priority'] or ''
|
|
ordered_meal = OrderedMeal.objects.create(order=order, meal=meal, gift=gift)
|
|
|
|
for ignored in range(quantity):
|
|
for food in meal.content.filter(available=True).all():
|
|
n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'],
|
|
order__note=order.note,
|
|
order__date__gte=timezone.now() - timedelta(hours=6),
|
|
food=food).exclude(status='CANCELED').count()
|
|
of = OrderedFood.objects.create(order=order, meal=ordered_meal, food=food,
|
|
remark=remark, priority=priority, number=n + 1, gift=0)
|
|
|
|
for option in food.foodoption_set.filter(available=True).all():
|
|
if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']:
|
|
of.options.add(option)
|
|
of.save()
|
|
|
|
first_food = ordered_meal.orderedfood_set.first()
|
|
tr = SheetOrderTransaction(source_id=order.note_id, destination=first_food.food.club.note,
|
|
source_alias=str(order.note), destination_alias=first_food.food.club.name,
|
|
quantity=quantity, ordered_food=first_food,
|
|
reason=f"{meal.name} - {sheet.name}")
|
|
tr.amount = tr.get_price / tr.quantity
|
|
tr.save()
|
|
|
|
for food in sheet.food_set.filter(available=True).all():
|
|
quantity = data[f'food_{food.id}_quantity']
|
|
if not quantity:
|
|
continue
|
|
|
|
total_quantity += quantity
|
|
gift = data[f'food_{meal.id}_gift']
|
|
remark = data[f'food_{meal.id}_remark'] or ''
|
|
priority = data[f'food_{meal.id}_priority'] or ''
|
|
|
|
for ignored in range(quantity):
|
|
n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'],
|
|
order__note=order.note,
|
|
order__date__gte=timezone.now() - timedelta(hours=6),
|
|
food=food).exclude(state='CANCELED').count()
|
|
of = OrderedFood.objects.create(order=order, food=food, gift=gift,
|
|
remark=remark, priority=priority, number=n + 1)
|
|
|
|
for option in food.foodoption_set.filter(available=True).all():
|
|
if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']:
|
|
of.options.add(option)
|
|
of.options.save()
|
|
|
|
tr = SheetOrderTransaction(source_id=order.note_id, destination_id=first_food.club.note,
|
|
source_alias=str(order.note), destination_alias=first_food.club.name,
|
|
quantity=quantity, ordered_food=of,
|
|
reason=f"{food.name} - {sheet.name}")
|
|
tr.amount = tr.get_price / tr.quantity
|
|
tr.save()
|
|
|
|
if total_quantity == 0:
|
|
form.add_error(None, _("You didn't select anything."))
|
|
transaction.rollback()
|
|
return self.form_invalid(form)
|
|
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],))
|