333 lines
9.8 KiB
Python
333 lines
9.8 KiB
Python
|
from reportlab.pdfgen import canvas
|
||
|
from reportlab.lib.pagesizes import A4
|
||
|
|
||
|
from django.conf import settings
|
||
|
|
||
|
from datetime import date, datetime, timedelta
|
||
|
from django.db.models import Q, Max
|
||
|
import os
|
||
|
|
||
|
import locale
|
||
|
|
||
|
from PIL import Image
|
||
|
|
||
|
from ultron.people.models import Gymnast
|
||
|
from ultron.followup.models import (
|
||
|
Plan,
|
||
|
Chrono,
|
||
|
Accident,
|
||
|
MindState,
|
||
|
HeightWeight,
|
||
|
)
|
||
|
|
||
|
from ultron.objective.models import Skill
|
||
|
from statistics import mean
|
||
|
|
||
|
import os
|
||
|
import yaml
|
||
|
import re
|
||
|
|
||
|
# EXPENSES = 0
|
||
|
# RECETTES = 1
|
||
|
|
||
|
X = 35
|
||
|
Y = 841.89
|
||
|
INDENT = 5
|
||
|
RIGHT_X = 595.27 - X
|
||
|
TITLED_X = 125
|
||
|
INDENTED_X = X + INDENT
|
||
|
INDENTED_RIGHT_X = RIGHT_X - INDENT
|
||
|
MIDDLE = (RIGHT_X - X) / 2
|
||
|
|
||
|
PRESTATION_COLUMN_2 = INDENTED_X + 65
|
||
|
PRESTATION_COLUMN_3 = INDENTED_X + 400
|
||
|
PRESTATION_COLUMN_4 = INDENTED_X + 455
|
||
|
|
||
|
COMMON_LINE_HEIGHT = -15
|
||
|
SMALL_LINE_HEIGHT = COMMON_LINE_HEIGHT + 5
|
||
|
LARGE_LINE_HEIGHT = COMMON_LINE_HEIGHT - 5
|
||
|
DOUBLE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 2
|
||
|
BIG_LINE_HEIGHT = COMMON_LINE_HEIGHT * 3
|
||
|
HUGE_LINE_HEIGHT = COMMON_LINE_HEIGHT * 4
|
||
|
|
||
|
|
||
|
class PDFDocument(object):
|
||
|
# Create the PDF object, using the response object as its "file."
|
||
|
# http://www.reportlab.com/docs/reportlab-userguide.pdf
|
||
|
# canvas.rect(x, y, width, height, stroke=1, fill=0)
|
||
|
# localhost:8000/billing/contract/pdf/2
|
||
|
|
||
|
def __init__(self, response):
|
||
|
# Create the PDF object, using the response object as its "file."
|
||
|
self.document = canvas.Canvas(response, pagesize=A4)
|
||
|
self.y = Y - X
|
||
|
self.__load_config()
|
||
|
|
||
|
def __load_config(self):
|
||
|
""" Charge le contenu du fichier SITE_CONFIG.YAML qui contient les données relatives à
|
||
|
l'ASBL.
|
||
|
"""
|
||
|
current_path = os.path.dirname(os.path.realpath(__file__))
|
||
|
self.club_infos = None
|
||
|
with open(os.path.join(current_path, "site_config.yaml"), "r") as stream:
|
||
|
try:
|
||
|
self.club_infos = yaml.load(stream, Loader=yaml.FullLoader)
|
||
|
except yaml.YAMLError as exc:
|
||
|
print(exc)
|
||
|
|
||
|
def add_vspace(self, height=COMMON_LINE_HEIGHT):
|
||
|
""" Passe à la ligne, la hauteur de la ligne étant passée en paramètre.
|
||
|
|
||
|
Args:
|
||
|
height (int): hauteur de la ligne.
|
||
|
|
||
|
Returns:
|
||
|
ne retourne rien.
|
||
|
"""
|
||
|
|
||
|
self.y += height
|
||
|
|
||
|
# if y < 120;
|
||
|
# document.PageBreak()
|
||
|
# y = 790
|
||
|
|
||
|
def add_string(
|
||
|
self, x, string, font_family="Helvetica", font_decoration=None, font_size=10
|
||
|
):
|
||
|
if font_decoration:
|
||
|
font_family += "-" + font_decoration
|
||
|
self.document.setFont(font_family, font_size)
|
||
|
self.document.drawString(x, self.y, string)
|
||
|
|
||
|
def add_new_line(
|
||
|
self,
|
||
|
x,
|
||
|
string,
|
||
|
height=COMMON_LINE_HEIGHT,
|
||
|
font_family="Helvetica",
|
||
|
font_decoration=None,
|
||
|
font_size=10,
|
||
|
):
|
||
|
self.add_vspace(height)
|
||
|
self.add_string(x, string, font_family, font_decoration, font_size)
|
||
|
|
||
|
def add_header(self, contract=None):
|
||
|
""" Génère le header du document.
|
||
|
|
||
|
Args:
|
||
|
contract (contract): instance de la class Contract.
|
||
|
|
||
|
Returns:
|
||
|
ne retourne rien.
|
||
|
"""
|
||
|
|
||
|
self.add_new_line(INDENTED_X, self.club_infos["SITE_TITLE"] + ' - ' + self.club_infos['CLUB_NAME'])
|
||
|
self.document.drawRightString(
|
||
|
INDENTED_RIGHT_X,
|
||
|
self.y,
|
||
|
"Head Coach : "
|
||
|
+ self.club_infos["HEAD_COACH"]
|
||
|
)
|
||
|
self.add_new_line(
|
||
|
INDENTED_X,
|
||
|
self.club_infos["ADDRESS"]
|
||
|
+ " - "
|
||
|
+ self.club_infos["CITY"]
|
||
|
+ " "
|
||
|
+ self.club_infos["ZIP"],
|
||
|
)
|
||
|
# N° de semaine
|
||
|
self.document.drawRightString(
|
||
|
INDENTED_RIGHT_X,
|
||
|
self.y,
|
||
|
self.club_infos["MOBILE_PHONE"]
|
||
|
)
|
||
|
# self.add_new_line(
|
||
|
# INDENTED_X,
|
||
|
# "Head Coach : "
|
||
|
# + self.club_infos["HEAD_COACH"]
|
||
|
# + " ("
|
||
|
# + self.club_infos["MOBILE_PHONE"]
|
||
|
# + ")"
|
||
|
# )
|
||
|
# if contract is not None:
|
||
|
# self.document.drawRightString(
|
||
|
# INDENTED_RIGHT_X, self.y, "N° de Référence : " + str(contract.reference)
|
||
|
# )
|
||
|
self.add_vspace(BIG_LINE_HEIGHT)
|
||
|
|
||
|
def download(self):
|
||
|
# Close the PDF object cleanly, and we're done.
|
||
|
self.document.showPage()
|
||
|
self.document.save()
|
||
|
|
||
|
|
||
|
class GymnastReportDocument(PDFDocument):
|
||
|
def generate(self, gymnast_id):
|
||
|
""" Genère un document aux normes du SPF Finance.
|
||
|
|
||
|
Args:
|
||
|
accounting_year (int): année comptable.
|
||
|
|
||
|
Returns:
|
||
|
ne retourne rien.
|
||
|
"""
|
||
|
gymnast = Gymnast.objects.get(pk=gymnast_id)
|
||
|
|
||
|
self.document.setTitle(gymnast.first_name + ' ' + gymnast.last_name)
|
||
|
|
||
|
self.add_header()
|
||
|
self.add_gymnast_personnal_information(gymnast)
|
||
|
self.add_gymnast_physiological_information(gymnast)
|
||
|
# self.add_gymnast_level_information(gymnast)
|
||
|
self.add_gymnast_best_scores(gymnast)
|
||
|
self.add_gymnast_next_skills(gymnast)
|
||
|
# self.add_gymnast_active_routine(gymnast)
|
||
|
|
||
|
def add_gymnast_personnal_information(self, gymnast):
|
||
|
""" Ajoute les informations personnelles du gymnast.
|
||
|
|
||
|
Args:
|
||
|
gymnast <Gymnast>: gymnaste
|
||
|
|
||
|
Returns:
|
||
|
ne retourne rien.
|
||
|
"""
|
||
|
|
||
|
# Rectangle pour la photo
|
||
|
rect_height = 80
|
||
|
self.document.rect(X, self.y - (rect_height + COMMON_LINE_HEIGHT), 60, rect_height, fill=0)
|
||
|
|
||
|
self.add_string(
|
||
|
110,
|
||
|
str(gymnast),
|
||
|
font_decoration="Bold",
|
||
|
font_size=16,
|
||
|
)
|
||
|
# self.add_vspace()
|
||
|
self.add_new_line(
|
||
|
110,
|
||
|
str(gymnast.age)
|
||
|
)
|
||
|
self.add_new_line(
|
||
|
110,
|
||
|
str(gymnast.informations)
|
||
|
)
|
||
|
self.add_vspace(HUGE_LINE_HEIGHT)
|
||
|
|
||
|
def add_gymnast_physiological_information(self, gymnast):
|
||
|
""" Ajoute les informations physique et psychologique.
|
||
|
|
||
|
Args:
|
||
|
gymnast <Gymnast>: gymnaste
|
||
|
|
||
|
Returns:
|
||
|
ne retourne rien
|
||
|
"""
|
||
|
last_mindstate = MindState.objects.filter(gymnast=gymnast).order_by('-date').first()
|
||
|
mean_mindstate = mean(MindState.objects.filter(gymnast=gymnast).order_by('-date').values_list("score", flat=True)[:5])
|
||
|
print(mean_mindstate)
|
||
|
if last_mindstate.score > mean_mindstate:
|
||
|
print('increasing')
|
||
|
elif last_mindstate.score < mean_mindstate:
|
||
|
print('decreasing')
|
||
|
else:
|
||
|
print('stabilised')
|
||
|
|
||
|
last_height_weigth = HeightWeight.objects.filter(gymnast=gymnast).order_by('-date').first()
|
||
|
mean_height = mean(HeightWeight.objects.filter(gymnast=gymnast).order_by('-date').values_list("height", flat=True)[:5])
|
||
|
mean_weight = mean(HeightWeight.objects.filter(gymnast=gymnast).order_by('-date').values_list("weight", flat=True)[:5])
|
||
|
print(mean_height)
|
||
|
if last_height_weigth.height > mean_height:
|
||
|
print('Height increasing')
|
||
|
elif last_height_weigth.height < mean_height:
|
||
|
print('Height decreasing')
|
||
|
else:
|
||
|
print('Height stabilised')
|
||
|
|
||
|
print(mean_weight)
|
||
|
if last_height_weigth.weight > mean_weight:
|
||
|
print('Weight increasing')
|
||
|
elif last_height_weigth.weight < mean_weight:
|
||
|
print('Weight decreasing')
|
||
|
else:
|
||
|
print('Weight stabilised')
|
||
|
|
||
|
last_accident = Accident.objects.filter(gymnast=gymnast).order_by("-date").first()
|
||
|
# print(last_accident)
|
||
|
|
||
|
def add_gymnast_best_scores(self, gymnast):
|
||
|
""" Ajoute les meilleurs scores du gymnaste (Tof, compétition, …).
|
||
|
|
||
|
Args:
|
||
|
gymnast <Gymnast>: gymnaste
|
||
|
|
||
|
Returns:
|
||
|
ne retourne rien
|
||
|
"""
|
||
|
|
||
|
lines = []
|
||
|
text_object = self.document.beginText()
|
||
|
text_object.setFont("Helvetica", 10)
|
||
|
text_object.setTextOrigin(INDENTED_X, self.y)
|
||
|
|
||
|
best_tof = (
|
||
|
Chrono.objects.filter(gymnast=gymnast)
|
||
|
.filter(chrono_type=0)
|
||
|
.order_by("-score")
|
||
|
.first()
|
||
|
)
|
||
|
lines.append("Best ToF |: " + str(best_tof.tof) + " (" + str(best_tof.date) + ")")
|
||
|
best_tof = (
|
||
|
Chrono.objects.filter(gymnast=gymnast)
|
||
|
.filter(chrono_type=1)
|
||
|
.order_by("-score")
|
||
|
.first()
|
||
|
)
|
||
|
lines.append("Best ToF R1: " + str(best_tof.tof) + " (" + str(best_tof.date) + ")")
|
||
|
best_tof = (
|
||
|
Chrono.objects.filter(gymnast=gymnast)
|
||
|
.filter(chrono_type=2)
|
||
|
.order_by("-score")
|
||
|
.first()
|
||
|
)
|
||
|
lines.append("Best ToF R2: " + str(best_tof.tof) + " (" + str(best_tof.date) + ")")
|
||
|
|
||
|
for line in lines:
|
||
|
text_object.textLine(line)
|
||
|
|
||
|
self.document.drawText(text_object)
|
||
|
|
||
|
# best_point_routine_1 = Point.objects.filter(gymnast=gymnast).filter(routine_type=1).order_by('-total').first()
|
||
|
# best_point_routine_2 = Point.objects.filter(gymnast=gymnast).filter(routine_type=2).order_by('-total').first()
|
||
|
|
||
|
# routine_1 = gymnast.has_routine.prefetch_related("routine").filter(
|
||
|
# dateend__isnull=True
|
||
|
# )
|
||
|
|
||
|
self.add_vspace()
|
||
|
|
||
|
def add_gymnast_next_skills(self, gymnast):
|
||
|
""" Ajoute les prochains skill (skill planifié) à apprendre
|
||
|
|
||
|
Args:
|
||
|
gymnast <Gymnast> gymnaste
|
||
|
|
||
|
Returns:
|
||
|
Ne retourne rien
|
||
|
"""
|
||
|
|
||
|
planified_skill = (
|
||
|
Skill.objects.filter(plan__gymnast=gymnast.id)
|
||
|
.filter(
|
||
|
Q(plan__is_done=False)
|
||
|
| Q(plan__date__gte=date.today())
|
||
|
)
|
||
|
.order_by("-plan__date")[:6]
|
||
|
)
|
||
|
|
||
|
for skill in planified_skill:
|
||
|
self.add_new_line(
|
||
|
X, skill.short_label + " (" + skill.notation + ") for (date)"
|
||
|
)
|