# # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr # # This file is part of the website of the UTBM Student Association (AE UTBM), # https://ae.utbm.fr. # # You can find the source code of the website at https://github.com/ae-utbm/sith # # LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE # OR WITHIN THE LOCAL FILE "LICENSE" # # from datetime import datetime, timedelta from django.conf import settings from django.contrib.auth.mixins import PermissionRequiredMixin, UserPassesTestMixin from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied from django.db import transaction from django.forms import CheckboxSelectMultiple from django.forms.models import modelform_factory from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.timezone import get_current_timezone from django.utils.translation import gettext as _ from django.views.generic import DetailView, ListView, TemplateView from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView from core.auth.mixins import CanViewMixin from core.utils import get_semester_code, get_start_of_semester from counter.forms import ( CloseCustomerAccountForm, CounterEditForm, ProductForm, ProductFormulaForm, ReturnableProductForm, ) from counter.models import ( Counter, Product, ProductFormula, ProductType, Refilling, ReturnableProduct, Selling, ) from counter.utils import is_logged_in_counter from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView): """A list view for the admins.""" model = Counter template_name = "counter/counter_list.jinja" current_tab = "counters" class CounterEditView( CounterAdminTabsMixin, UserPassesTestMixin, SuccessMessageMixin, UpdateView ): """Edit a counter's main informations (for the counter's manager).""" model = Counter form_class = CounterEditForm pk_url_kwarg = "counter_id" template_name = "core/edit.jinja" current_tab = "counters" success_message = _("Counter update done") def test_func(self): if self.request.user.has_perm("counter.change_counter"): return True obj = self.get_object(queryset=self.get_queryset().select_related("club")) return obj.club.has_rights_in_club(self.request.user) def get_form_kwargs(self): return super().get_form_kwargs() | {"user": self.request.user} def get_success_url(self): return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id}) class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """Edit a counter's main informations (for the counter's admin).""" model = Counter form_class = modelform_factory(Counter, fields=["name", "club", "type"]) pk_url_kwarg = "counter_id" template_name = "core/edit.jinja" current_tab = "counters" class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """Create a counter (for the admins).""" model = Counter form_class = modelform_factory( Counter, fields=["name", "club", "type", "products"], widgets={"products": CheckboxSelectMultiple}, ) template_name = "core/create.jinja" current_tab = "counters" class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView): """Delete a counter (for the admins).""" model = Counter pk_url_kwarg = "counter_id" template_name = "core/delete_confirm.jinja" success_url = reverse_lazy("counter:admin_list") current_tab = "counters" # Product management class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """A list view for the admins.""" model = ProductType template_name = "counter/product_type_list.jinja" current_tab = "product_types" context_object_name = "product_types" class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """A create view for the admins.""" model = ProductType fields = ["name", "description", "comment", "icon"] template_name = "core/create.jinja" current_tab = "products" class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """An edit view for the admins.""" model = ProductType template_name = "core/edit.jinja" fields = ["name", "description", "comment", "icon"] pk_url_kwarg = "type_id" current_tab = "products" class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView): current_tab = "products" template_name = "counter/product_list.jinja" class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView): """A create view for the admins.""" model = Product form_class = ProductForm template_name = "counter/product_form.jinja" current_tab = "products" class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView): """An edit view for the admins.""" model = Product form_class = ProductForm pk_url_kwarg = "product_id" template_name = "counter/product_form.jinja" current_tab = "products" class ProductFormulaListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView): model = ProductFormula queryset = ProductFormula.objects.select_related("result").prefetch_related( "products" ) template_name = "counter/formula_list.jinja" current_tab = "formulas" permission_required = "counter.view_productformula" class ProductFormulaCreateView( CounterAdminTabsMixin, PermissionRequiredMixin, CreateView ): model = ProductFormula form_class = ProductFormulaForm pk_url_kwarg = "formula_id" template_name = "core/create.jinja" current_tab = "formulas" success_url = reverse_lazy("counter:product_formula_list") permission_required = "counter.add_productformula" class ProductFormulaEditView( CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView ): model = ProductFormula form_class = ProductFormulaForm pk_url_kwarg = "formula_id" template_name = "core/edit.jinja" current_tab = "formulas" success_url = reverse_lazy("counter:product_formula_list") permission_required = "counter.change_productformula" class ProductFormulaDeleteView( CounterAdminTabsMixin, PermissionRequiredMixin, DeleteView ): model = ProductFormula pk_url_kwarg = "formula_id" template_name = "core/delete_confirm.jinja" current_tab = "formulas" success_url = reverse_lazy("counter:product_formula_list") permission_required = "counter.delete_productformula" def get_context_data(self, **kwargs): obj_name = self.object.result.name return super().get_context_data(**kwargs) | { "object_name": _("%(formula)s (formula)") % {"formula": obj_name}, "help_text": _( "This action will only delete the formula, " "but not the %(product)s product itself." ) % {"product": obj_name}, } class ReturnableProductListView( CounterAdminTabsMixin, PermissionRequiredMixin, ListView ): model = ReturnableProduct queryset = model.objects.select_related("product", "returned_product") template_name = "counter/returnable_list.jinja" current_tab = "returnable_products" permission_required = "counter.view_returnableproduct" class ReturnableProductCreateView( CounterAdminTabsMixin, PermissionRequiredMixin, CreateView ): form_class = ReturnableProductForm template_name = "core/create.jinja" current_tab = "returnable_products" success_url = reverse_lazy("counter:returnable_list") permission_required = "counter.add_returnableproduct" class ReturnableProductUpdateView( CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView ): model = ReturnableProduct pk_url_kwarg = "returnable_id" queryset = model.objects.select_related("product", "returned_product") form_class = ReturnableProductForm template_name = "core/edit.jinja" current_tab = "returnable_products" success_url = reverse_lazy("counter:returnable_list") permission_required = "counter.change_returnableproduct" def get_context_data(self, **kwargs): return super().get_context_data(**kwargs) | { "object_name": _("returnable product : %(returnable)s -> %(returned)s") % { "returnable": self.object.product.name, "returned": self.object.returned_product.name, } } class ReturnableProductDeleteView( CounterAdminTabsMixin, PermissionRequiredMixin, DeleteView ): model = ReturnableProduct pk_url_kwarg = "returnable_id" queryset = model.objects.select_related("product", "returned_product") template_name = "core/delete_confirm.jinja" current_tab = "returnable_products" success_url = reverse_lazy("counter:returnable_list") permission_required = "counter.delete_returnableproduct" def get_context_data(self, **kwargs): return super().get_context_data(**kwargs) | { "object_name": _("returnable product : %(returnable)s -> %(returned)s") % { "returnable": self.object.product.name, "returned": self.object.returned_product.name, } } class RefillingDeleteView(DeleteView): """Delete a refilling (for the admins).""" model = Refilling pk_url_kwarg = "refilling_id" template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" self.object = self.get_object() if timezone.now() - self.object.date <= timedelta( minutes=settings.SITH_LAST_OPERATIONS_LIMIT ) and is_logged_in_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) return super().dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): self.success_url = reverse( "core:user_account", kwargs={"user_id": self.object.customer.user.id} ) return super().dispatch(request, *args, **kwargs) raise PermissionDenied class SellingDeleteView(DeleteView): """Delete a selling (for the admins).""" model = Selling pk_url_kwarg = "selling_id" template_name = "core/delete_confirm.jinja" def dispatch(self, request, *args, **kwargs): """We have here a very particular right handling, we can't inherit from CanEditPropMixin.""" self.object = self.get_object() if timezone.now() - self.object.date <= timedelta( minutes=settings.SITH_LAST_OPERATIONS_LIMIT ) and is_logged_in_counter(request): self.success_url = reverse( "counter:details", kwargs={"counter_id": self.object.counter.id} ) return super().dispatch(request, *args, **kwargs) elif self.object.is_owned_by(request.user): self.success_url = reverse( "core:user_account", kwargs={"user_id": self.object.customer.user.id} ) return super().dispatch(request, *args, **kwargs) raise PermissionDenied class CounterStatView(PermissionRequiredMixin, DetailView): """Show the bar stats.""" model = Counter pk_url_kwarg = "counter_id" template_name = "counter/stats.jinja" permission_required = "counter.view_counter_stats" def get_context_data(self, **kwargs): """Add stats to the context.""" counter: Counter = self.object start_date = get_start_of_semester() semester_start = datetime( start_date.year, start_date.month, start_date.day, tzinfo=get_current_timezone(), ) office_hours = counter.get_top_barmen() kwargs = super().get_context_data(**kwargs) kwargs.update( { "counter": counter, "current_semester": get_semester_code(), "total_sellings": counter.get_total_sales(since=semester_start), "top_customers": counter.get_top_customers(since=semester_start)[:100], "top_barman": office_hours[:100], "top_barman_semester": ( office_hours.filter(start__gt=semester_start)[:100] ), } ) return kwargs class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListView): """List of refillings on a counter.""" model = Refilling template_name = "counter/refilling_list.jinja" current_tab = "counters" paginate_by = 30 def dispatch(self, request, *args, **kwargs): self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"]) self.queryset = Refilling.objects.filter(counter__id=self.counter.id) return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): kwargs = super().get_context_data(**kwargs) kwargs["counter"] = self.counter return kwargs class RefoundAccountView(UserPassesTestMixin, FormView): """Create a selling with the same amount as the current user money.""" template_name = "counter/refound_account.jinja" form_class = CloseCustomerAccountForm def test_func(self): return self.request.user.is_root or self.request.user.is_in_group( pk=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID ) def form_valid(self, form): self.customer = form.cleaned_data["user"] self.create_selling() return super().form_valid(form) def get_success_url(self): return self.request.path def create_selling(self): with transaction.atomic(): uprice = self.customer.customer.amount refound_club_counter = Counter.objects.get( id=settings.SITH_COUNTER_REFOUND_ID ) refound_club = refound_club_counter.club s = Selling( label=_("Refound account"), unit_price=uprice, quantity=1, seller=self.request.user, customer=self.customer.customer, club=refound_club, counter=refound_club_counter, product=Product.objects.get(id=settings.SITH_PRODUCT_REFOUND_ID), ) s.save()