2024-04-30 12:27:17 +02:00
|
|
|
from datetime import date, timedelta
|
|
|
|
from decimal import Decimal
|
|
|
|
|
|
|
|
from django.contrib import admin, messages
|
|
|
|
from django.shortcuts import redirect
|
|
|
|
from django.urls import reverse
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
|
|
|
|
import nested_admin
|
|
|
|
|
|
|
|
from . import models
|
|
|
|
|
|
|
|
|
|
|
|
class MyAdminSite(admin.AdminSite):
|
|
|
|
site_header = "Fournée 2.0"
|
|
|
|
index_title = (
|
|
|
|
"Une interface de gestion offerte par Django (et un peu de sueur)"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
admin_site = MyAdminSite(name="admin")
|
|
|
|
|
|
|
|
|
|
|
|
class PainAdmin(admin.ModelAdmin):
|
|
|
|
list_display = ["nom", "pâte", "poids", "moulé"]
|
|
|
|
list_editable = ["pâte", "poids", "moulé"]
|
|
|
|
list_select_related = ["pâte"]
|
|
|
|
actions = None
|
|
|
|
save_as = True
|
|
|
|
|
|
|
|
|
|
|
|
admin_site.register(models.Pain, PainAdmin)
|
|
|
|
|
|
|
|
|
|
|
|
class IngrédientInline(admin.TabularInline):
|
|
|
|
model = models.Ingrédient
|
|
|
|
sortable_field_name = "ordre"
|
|
|
|
extra = 1
|
|
|
|
|
|
|
|
|
|
|
|
class RecetteAdmin(admin.ModelAdmin):
|
|
|
|
inlines = [
|
|
|
|
IngrédientInline,
|
|
|
|
]
|
|
|
|
actions = None
|
|
|
|
save_as = True
|
|
|
|
|
|
|
|
|
|
|
|
admin_site.register(models.Recette, RecetteAdmin)
|
|
|
|
|
|
|
|
|
|
|
|
class MyDateFieldListFilter(admin.filters.DateFieldListFilter):
|
|
|
|
def __init__(self, field, request, params, model, model_admin, field_path):
|
|
|
|
super().__init__(field, request, params, model, model_admin, field_path)
|
|
|
|
self.links = (
|
|
|
|
self.links[0:1]
|
|
|
|
+ (
|
|
|
|
(
|
|
|
|
"Demain",
|
|
|
|
{
|
|
|
|
self.lookup_kwarg_since: str(
|
|
|
|
date.today() + timedelta(days=1)
|
|
|
|
),
|
|
|
|
self.lookup_kwarg_until: str(
|
|
|
|
date.today() + timedelta(days=2)
|
|
|
|
),
|
|
|
|
},
|
|
|
|
),
|
|
|
|
)
|
|
|
|
+ self.links[1:]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class RéservationAdmin(admin.ModelAdmin):
|
|
|
|
model = models.Réservation
|
|
|
|
actions = None
|
|
|
|
list_display = ["pour", "date", "pain", "quantité", "fournée"]
|
|
|
|
list_display_links = ["fournée"]
|
|
|
|
list_filter = [
|
|
|
|
("commande__fournée__date", MyDateFieldListFilter),
|
|
|
|
"commande__pour",
|
|
|
|
"commande__fournée__pour",
|
|
|
|
"pain",
|
|
|
|
]
|
|
|
|
list_editable = ["pain", "quantité"]
|
|
|
|
list_select_related = ["commande", "commande__fournée"]
|
|
|
|
|
|
|
|
@admin.display(ordering="commande__pour")
|
|
|
|
def pour(self, obj):
|
|
|
|
return obj.commande.pour
|
|
|
|
|
|
|
|
@admin.display(ordering="commande__fournée__pour")
|
|
|
|
def fournée(self, obj):
|
|
|
|
return mark_safe(
|
|
|
|
'<a href="{}">{}</a>'.format(
|
|
|
|
reverse(
|
|
|
|
"admin:core_fournée_change", args=(obj.commande.fournée.pk,)
|
|
|
|
),
|
|
|
|
obj.commande.fournée.pour,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
@admin.display(ordering="commande__fournée__date")
|
|
|
|
def date(self, obj):
|
|
|
|
return obj.commande.fournée.date
|
|
|
|
|
|
|
|
|
|
|
|
admin_site.register(models.Réservation, RéservationAdmin)
|
|
|
|
|
|
|
|
|
|
|
|
class RéservationInline(nested_admin.NestedTabularInline):
|
|
|
|
model = models.Réservation
|
|
|
|
extra = 1
|
|
|
|
classes = ["collapse"]
|
|
|
|
|
|
|
|
|
|
|
|
class CommandeInline(nested_admin.NestedStackedInline):
|
|
|
|
model = models.Commande
|
|
|
|
inlines = [
|
|
|
|
RéservationInline,
|
|
|
|
]
|
|
|
|
extra = 1
|
|
|
|
classes = ["collapse"]
|
|
|
|
|
|
|
|
|
|
|
|
class CouléeInline(
|
|
|
|
nested_admin.SortableHiddenMixin, nested_admin.NestedTabularInline
|
|
|
|
):
|
|
|
|
model = models.Coulée
|
|
|
|
extra = 0
|
|
|
|
classes = ["collapse"]
|
|
|
|
sortable_field_name = "ordre"
|
|
|
|
|
|
|
|
|
|
|
|
class FournéeAdmin(nested_admin.NestedModelAdmin):
|
|
|
|
change_form_template = "admin/fournée_change_form.html"
|
|
|
|
inlines = [
|
|
|
|
CommandeInline,
|
|
|
|
CouléeInline,
|
|
|
|
]
|
|
|
|
actions = None
|
|
|
|
extra = 1
|
|
|
|
list_filter = [("date", MyDateFieldListFilter), "commande__fournée__pour"]
|
|
|
|
date_hierarchy = "date"
|
|
|
|
save_as = True
|
|
|
|
save_on_top = True
|
|
|
|
|
|
|
|
def save_related(self, request, form, formsets, change):
|
|
|
|
"""Création des coulées manquantes"""
|
|
|
|
super().save_related(request, form, formsets, change)
|
|
|
|
if change and "_continueandcreatecoulees" in request.POST:
|
|
|
|
obj = form.instance
|
|
|
|
|
|
|
|
"""Pour chaque pâte on regarde si les coulées sont complètes.
|
|
|
|
Si elles ne le sont pas, on ajoute ce qui manque."""
|
|
|
|
for pâte_id, pâte_nom in (
|
|
|
|
obj.commande_set.values_list(
|
|
|
|
"réservation__pain__pâte", "réservation__pain__pâte__nom"
|
|
|
|
)
|
|
|
|
.order_by("réservation__pain__pâte")
|
|
|
|
.distinct()
|
|
|
|
):
|
|
|
|
somme_commandée = obj.masse_commandée(pâte_id)
|
|
|
|
somme_coulée = sum(
|
|
|
|
obj.coulée_set.filter(pâte_id=pâte_id).values_list(
|
|
|
|
"quantité", flat=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
pâte_manquante = somme_commandée - somme_coulée
|
|
|
|
if pâte_manquante > 0:
|
|
|
|
obj.coulée_set.create(
|
|
|
|
pâte_id=pâte_id, quantité=pâte_manquante
|
|
|
|
)
|
|
|
|
self.message_user(
|
|
|
|
request,
|
|
|
|
f"Création d'une coulée de {pâte_manquante.normalize()} kg pour la pâte {pâte_nom}.",
|
|
|
|
)
|
|
|
|
elif pâte_manquante < 0:
|
|
|
|
self.message_user(
|
|
|
|
request,
|
|
|
|
f"Attention la pâte {pâte_nom} est prévue en excès ({-pâte_manquante.normalize()} kg en trop)",
|
|
|
|
messages.WARNING,
|
|
|
|
)
|
|
|
|
|
|
|
|
def save_model(self, request, obj, form, change):
|
|
|
|
"""Décale d'une semaine en cas de save_as si c'est utile"""
|
|
|
|
if not change:
|
|
|
|
date = form.cleaned_data.get("date", None)
|
|
|
|
if obj._meta.model.objects.filter(date=date).exists():
|
|
|
|
prochaine_date = date + timedelta(weeks=1)
|
|
|
|
if not obj._meta.model.objects.filter(
|
|
|
|
date=prochaine_date
|
|
|
|
).exists():
|
|
|
|
obj.date = date + timedelta(weeks=1)
|
|
|
|
|
|
|
|
super().save_model(request, obj, form, change)
|
|
|
|
|
|
|
|
def response_post_save_change(self, request, obj):
|
|
|
|
"""Hack la réponse pour forcer un _continue sans hacker tout
|
|
|
|
le fichier nested_admin/nested.py"""
|
|
|
|
if "_continueandcreatecoulees" in request.POST:
|
|
|
|
return redirect("admin:core_fournée_change", obj.pk)
|
|
|
|
else:
|
|
|
|
return super().response_change(request, obj)
|
|
|
|
|
|
|
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
|
|
|
extra_context = extra_context or {}
|
|
|
|
obj = self.model.objects.get(pk=object_id)
|
|
|
|
extra_context["commandes"] = obj.commande_set.all()
|
|
|
|
coulées = obj.coulée_set.all()
|
|
|
|
|
2024-05-12 19:20:15 +02:00
|
|
|
""" on créé les tableaux coulée / ingrédient """
|
2024-04-30 12:27:17 +02:00
|
|
|
extra_context["coulées"] = []
|
|
|
|
for c in obj.coulée_set.all():
|
2024-05-12 19:20:15 +02:00
|
|
|
quantités = [c, ]
|
|
|
|
ingrédients = c.pâte.ingrédient_set.values_list('nom', flat=True)
|
|
|
|
for i in ingrédients:
|
|
|
|
quantités.append((c.quantité / c.pâte.poids_de_base * sum(
|
|
|
|
c.pâte.ingrédient_set.filter(nom=i).values_list("quantité", flat=True)
|
|
|
|
)).quantize(Decimal('1.000')).normalize())
|
|
|
|
|
2024-04-30 12:27:17 +02:00
|
|
|
masse_coulée = sum(
|
|
|
|
obj.coulée_set.filter(pâte=c.pâte).values_list(
|
|
|
|
"quantité", flat=True
|
|
|
|
)
|
|
|
|
)
|
|
|
|
masse_commandée = obj.masse_commandée(c.pâte_id)
|
|
|
|
if masse_coulée == masse_commandée:
|
|
|
|
problems = ""
|
|
|
|
elif masse_coulée > masse_commandée:
|
|
|
|
problems = f"{(masse_coulée - masse_commandée).normalize()} kg coulés en trop !!"
|
|
|
|
elif masse_coulée < masse_commandée:
|
|
|
|
problems = f"{(masse_commandée - masse_coulée).normalize()} kg coulés en moins !!"
|
2024-05-12 19:20:15 +02:00
|
|
|
extra_context["coulées"].append([problems, ingrédients, quantités])
|
2024-04-30 12:27:17 +02:00
|
|
|
|
2024-05-12 19:20:15 +02:00
|
|
|
""" on créé un tableau total coulée / ingrédient """
|
|
|
|
ingrédients = sorted(
|
|
|
|
set(
|
|
|
|
coulées.values_list(
|
|
|
|
"pâte__ingrédient__nom", "pâte__ingrédient__ordre"
|
|
|
|
)
|
|
|
|
),
|
|
|
|
key=lambda x: x[1],
|
|
|
|
)
|
|
|
|
extra_context["ingrédients"] = ingrédients
|
|
|
|
|
2024-05-12 22:26:26 +02:00
|
|
|
pains = models.Pain.objects.filter(pk__in=obj.commande_set.exclude(réservation__isnull=True).values_list("pains", flat=True)).values_list("nom", flat=True)
|
|
|
|
totaux = [sum(obj.coulée_set.values_list('quantité', flat=True))]
|
2024-04-30 12:27:17 +02:00
|
|
|
for i, _ in ingrédients:
|
|
|
|
total = sum([
|
|
|
|
qté * qté_unit / unit
|
|
|
|
for qté, qté_unit, unit in
|
|
|
|
obj.coulée_set.filter(pâte__ingrédient__nom=i)
|
|
|
|
.values_list('quantité', 'pâte__ingrédient__quantité', 'pâte__poids_de_base')
|
|
|
|
]).quantize(Decimal('1.000')).normalize()
|
|
|
|
totaux.append(total)
|
|
|
|
extra_context["totaux_coulées"] = totaux
|
|
|
|
|
|
|
|
""" liste des pains, vérifiant s'ils sont coulés """
|
|
|
|
extra_context["pains"] = [
|
|
|
|
(not obj.coulée_set.filter(pâte__pain__nom=p).exists(), p)
|
|
|
|
for p in pains
|
|
|
|
]
|
|
|
|
""" on créé un tableau pains / commande """
|
|
|
|
extra_context["divisions"] = []
|
|
|
|
for c in obj.commande_set.all():
|
|
|
|
tmp = [c.pour]
|
|
|
|
for p in pains:
|
|
|
|
if c.réservation_set.filter(pain__nom=p).exists():
|
|
|
|
tmp.append(
|
|
|
|
sum(
|
|
|
|
c.réservation_set.filter(pain__nom=p).values_list(
|
|
|
|
"quantité", flat=True
|
|
|
|
)
|
|
|
|
).normalize()
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
tmp.append("")
|
|
|
|
extra_context["divisions"].append(tmp)
|
2024-05-12 22:26:26 +02:00
|
|
|
totaux = [sum(obj.coulée_set.values_list('quantité', flat=True))]
|
2024-04-30 12:27:17 +02:00
|
|
|
for p in pains:
|
|
|
|
total = sum(
|
|
|
|
obj.commande_set.filter(pains__nom=p).values_list(
|
|
|
|
"réservation__quantité", flat=True
|
|
|
|
)
|
|
|
|
).normalize()
|
|
|
|
totaux.append(total)
|
|
|
|
extra_context["totaux_divisions"] = totaux
|
|
|
|
|
|
|
|
return super().change_view(
|
|
|
|
request,
|
|
|
|
object_id,
|
|
|
|
form_url,
|
|
|
|
extra_context=extra_context,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
admin_site.register(models.Fournée, FournéeAdmin)
|
|
|
|
|
|
|
|
|
|
|
|
class CouléeAdmin(admin.ModelAdmin):
|
|
|
|
model = models.Coulée
|
|
|
|
actions = None
|
|
|
|
list_display = ["pâte", "quantité", "date", "fournée_pour"]
|
|
|
|
list_display_links = ["fournée_pour"]
|
|
|
|
list_filter = [("fournée__date", MyDateFieldListFilter), "pâte"]
|
|
|
|
list_editable = ["pâte", "quantité"]
|
|
|
|
list_select_related = ["fournée"]
|
|
|
|
|
|
|
|
@admin.display(ordering="fournée__pour")
|
|
|
|
def fournée_pour(self, obj):
|
|
|
|
return mark_safe(
|
|
|
|
'<a href="{}">{}</a>'.format(
|
|
|
|
reverse("admin:core_fournée_change", args=(obj.fournée.pk,)),
|
|
|
|
obj.fournée.pour,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
@admin.display(ordering="fournée__date")
|
|
|
|
def date(self, obj):
|
|
|
|
return obj.fournée.date
|
|
|
|
|
|
|
|
|
|
|
|
admin_site.register(models.Coulée, CouléeAdmin)
|