On utilise Scale

This commit is contained in:
Tom Hubrecht 2021-06-26 13:34:14 +02:00
parent cecbb583c6
commit ad8c3143b6
2 changed files with 49 additions and 72 deletions

View file

@ -238,12 +238,7 @@ urlpatterns = [
# -----
# Sales history urls
# -----
path("purchases", views.purchases_csv, name="kfet.purchases"),
path(
"purchases/<int:start_year>-<int:start_month>/<int:end_year>-<int:end_month>",
views.purchases_csv,
name="kfet.purchases",
),
path("purchases", views.SalesStatList.as_view(), name="kfet.purchases"),
# -----
# JSON urls
# -----

View file

@ -2,7 +2,7 @@ import csv
import heapq
import statistics
from collections import defaultdict
from datetime import date, datetime, timedelta
from datetime import datetime, timedelta
from decimal import Decimal
from typing import List
from urllib.parse import urlencode
@ -13,12 +13,13 @@ from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.contrib.auth.models import Permission, User
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import SuspiciousOperation, ValidationError
from django.core.exceptions import SuspiciousOperation
from django.db import transaction
from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery, Sum
from django.forms import ValidationError, formset_factory
from django.http import (
Http404,
HttpResponse,
HttpResponseBadRequest,
HttpResponseForbidden,
JsonResponse,
@ -30,6 +31,7 @@ from django.utils.decorators import method_decorator
from django.views.generic import DetailView, FormView, ListView, TemplateView
from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.views.generic.list import BaseListView
from gestioncof.models import CofProfile
from kfet import KFET_DELETED_TRIGRAMME, consumers
@ -2456,81 +2458,61 @@ class ScaleMixin(object):
# Export des ventes en CSV
# ------------------------
MONTHS = (
"_Janvier_Février_Mars_Avril_Mai_Juin_Juillet_Août_Septembre_Octobre"
"_Novembre_Décembre"
).split("_")
@teamkfet_required
def purchases_csv(
request, start_year=None, start_month=None, end_year=None, end_month=None
):
"""
Renvoie un historique des achats en K-Fêt, par mois et sur une période donnée
(Par défault les 12 mois précédents)
"""
# Si on ne spécifie pas tout l'intervalle, on prend les 12 derniers mois
if (start_year and start_month and end_year and end_month) is None:
# On utilise replace pour gérer le cas des années bissextiles sans
# changer de mois
end = date.today().replace(day=1)
start = end - timedelta(days=365)
start_year = start.year
start_month = start.month
end_year = end.year
end_month = end.month
# On calcule la liste des premiers jours des mois dont on veut avoir les
# ventes
dates = []
@method_decorator(teamkfet_required, name="dispatch")
class SalesStatList(BaseListView):
model = Operation
for year in range(start_year, end_year + 1):
month_start = start_month if year == start_year else 1
month_end = end_month if year == end_year else 12
for month in range(month_start, month_end + 1):
dates.append(date(year, month, 1))
def get_queryset(self):
return super().get_queryset().filter(type=Operation.PURCHASE, canceled_at=None)
# On rajoute le mois suivant
dates.append((dates[-1] + timedelta(days=31)).replace(day=1))
# On récupère les données voulues
articles_list = list(Article.objects.order_by("name"))
nb_articles = len(articles_list)
articles = {}
for i in range(nb_articles):
articles[articles_list[i].name] = i + 1
ventes = (
Operation.objects.filter(
type="purchase", canceled_by=None, group__at__range=[dates[0], dates[-1]]
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
scale = MonthScale(n_steps=12, last=True)
qs = self.get_queryset()
articles = list(Article.objects.order_by("name"))
indexes = {a.name: index for index, a in enumerate(articles)}
ventes = scale.chunkify_qs(
qs.values_list(
"article__name", "group__at__month", "group__at__year"
).annotate(Sum("article_nb")),
field="group__at",
)
.exclude(article=None)
.order_by("group__at")
.select_related("group", "article")
)
# On crée le fichier CSV
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="historique_ventes.csv"'
writer = csv.writer(response)
ctx["header"] = ["Mois"] + [a.name for a in articles]
ctx["ventes"] = []
ctx["total"] = [0] * len(articles)
for v in ventes:
m_ventes = [0] * len(articles)
for (a_name, month, year, nb_ventes) in v:
index = indexes[a_name]
m_ventes[index] = nb_ventes
ctx["total"][index] += nb_ventes
# On met un article par colonne
header = ["Mois"] + [a for a in articles]
writer.writerow(header)
ctx["ventes"].append(["{} {}".format(MONTHS[month], year)] + m_ventes)
# On calcule la consommation par mois sur la période donnée
i_m = 0
row = [f"{dates[i_m].month:02d}/{dates[i_m].year}"] + [0] * nb_articles
last_row = ["Total"] + [0] * nb_articles
for v in ventes:
while v.group.at.date() > dates[i_m + 1]:
writer.writerow(row)
i_m += 1
row = [f"{dates[i_m].month:02d}/{dates[i_m].year}"] + [0] * nb_articles
return ctx
row[articles[v.article.name]] += v.article_nb
last_row[articles[v.article.name]] += v.article_nb
def render_to_response(self, context):
response = HttpResponse(content_type="text/csv")
response["Content-Disposition"] = 'attachment; filename="historique_ventes.csv"'
writer.writerow(row)
writer.writerow(last_row)
writer = csv.writer(response)
writer.writerow(context["header"])
return response
# On écrit chaque mois
for line in context["ventes"]:
writer.writerow(line)
writer.writerow([])
writer.writerow(["Total"] + context["total"])
return response
# -----------------------