forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -640,9 +640,9 @@ def justif_import(justif_id: int = None):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return {"filename": fname}
|
return {"filename": fname}
|
||||||
except ScoValueError as err:
|
except ScoValueError as exc:
|
||||||
# Si cela ne fonctionne pas on renvoie une erreur
|
# Si cela ne fonctionne pas on renvoie une erreur
|
||||||
return json_error(404, err.args[0])
|
return json_error(404, exc.args[0])
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"])
|
@bp.route("/justificatif/<int:justif_id>/export/<filename>", methods=["GET", "POST"])
|
||||||
|
@ -26,9 +26,11 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Formulaire ajout d'une "assiduité" sur un étudiant
|
Formulaire ajout d'une "assiduité" sur un étudiant
|
||||||
|
Formulaire ajout d'un justificatif sur un étudiant
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_wtf.file import MultipleFileField
|
||||||
from wtforms import (
|
from wtforms import (
|
||||||
SelectField,
|
SelectField,
|
||||||
StringField,
|
StringField,
|
||||||
@ -37,20 +39,15 @@ from wtforms import (
|
|||||||
TextAreaField,
|
TextAreaField,
|
||||||
validators,
|
validators,
|
||||||
)
|
)
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
|
||||||
class AjoutAssiduiteEtudForm(FlaskForm):
|
class AjoutAssiOrJustForm(FlaskForm):
|
||||||
"Formulaire de saisie d'une assiduité pour un étudiant"
|
"""Elements communs aux deux formulaires ajout
|
||||||
|
assiduité et justificatif
|
||||||
|
"""
|
||||||
|
|
||||||
error_message = "" # used to report our errors
|
error_message = "" # used to report our errors
|
||||||
|
|
||||||
assi_etat = RadioField(
|
|
||||||
"Signaler:",
|
|
||||||
choices=[("absent", "absence"), ("retard", "retard"), ("present", "présence")],
|
|
||||||
default="absent",
|
|
||||||
validators=[
|
|
||||||
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
date_debut = StringField(
|
date_debut = StringField(
|
||||||
"Date de début",
|
"Date de début",
|
||||||
validators=[validators.Length(max=10)],
|
validators=[validators.Length(max=10)],
|
||||||
@ -89,14 +86,9 @@ class AjoutAssiduiteEtudForm(FlaskForm):
|
|||||||
"id": "assi_date_fin",
|
"id": "assi_date_fin",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
modimpl = SelectField(
|
|
||||||
"Module",
|
|
||||||
choices={}, # will be populated dynamically
|
|
||||||
)
|
|
||||||
assi_raison = TextAreaField(
|
assi_raison = TextAreaField(
|
||||||
"Raison",
|
"Raison",
|
||||||
render_kw={
|
render_kw={
|
||||||
# "name": "assi_raison",
|
|
||||||
"id": "assi_raison",
|
"id": "assi_raison",
|
||||||
"cols": 75,
|
"cols": 75,
|
||||||
"rows": 4,
|
"rows": 4,
|
||||||
@ -114,3 +106,37 @@ class AjoutAssiduiteEtudForm(FlaskForm):
|
|||||||
)
|
)
|
||||||
submit = SubmitField("Enregistrer")
|
submit = SubmitField("Enregistrer")
|
||||||
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
||||||
|
|
||||||
|
|
||||||
|
class AjoutAssiduiteEtudForm(AjoutAssiOrJustForm):
|
||||||
|
"Formulaire de saisie d'une assiduité pour un étudiant"
|
||||||
|
|
||||||
|
assi_etat = RadioField(
|
||||||
|
"Signaler:",
|
||||||
|
choices=[("absent", "absence"), ("retard", "retard"), ("present", "présence")],
|
||||||
|
default="absent",
|
||||||
|
validators=[
|
||||||
|
validators.DataRequired("spécifiez le type d'évènement à signaler"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
modimpl = SelectField(
|
||||||
|
"Module",
|
||||||
|
choices={}, # will be populated dynamically
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AjoutJustificatifEtudForm(AjoutAssiOrJustForm):
|
||||||
|
"Formulaire de saisie d'un justificatif pour un étudiant"
|
||||||
|
|
||||||
|
etat = SelectField(
|
||||||
|
"État du justificatif",
|
||||||
|
choices=[
|
||||||
|
("", "Choisir..."), # Placeholder
|
||||||
|
("attente", "En attente de validation"),
|
||||||
|
("non_valide", "Non valide"),
|
||||||
|
("modifie", "Modifié"),
|
||||||
|
("valide", "Valide"),
|
||||||
|
],
|
||||||
|
validators=[DataRequired(message="This field is required.")],
|
||||||
|
)
|
||||||
|
fichiers = MultipleFileField()
|
||||||
|
@ -373,13 +373,13 @@ class Justificatif(db.Model):
|
|||||||
}
|
}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
"chaine pour journaux et debug (lisible par humain français)"
|
"chaine pour journaux et debug (lisible par humain français)"
|
||||||
try:
|
try:
|
||||||
etat_str = EtatJustificatif(self.etat).name
|
etat_str = EtatJustificatif(self.etat).name
|
||||||
except ValueError:
|
except ValueError:
|
||||||
etat_str = "Invalide"
|
etat_str = "Invalide"
|
||||||
return f"""Justificatif {etat_str} de {
|
return f"""Justificatif id={self.id} {etat_str} de {
|
||||||
self.date_debut.strftime("%d/%m/%Y %Hh%M")
|
self.date_debut.strftime("%d/%m/%Y %Hh%M")
|
||||||
} à {
|
} à {
|
||||||
self.date_fin.strftime("%d/%m/%Y %Hh%M")
|
self.date_fin.strftime("%d/%m/%Y %Hh%M")
|
||||||
@ -411,7 +411,7 @@ class Justificatif(db.Model):
|
|||||||
|
|
||||||
db.session.add(nouv_justificatif)
|
db.session.add(nouv_justificatif)
|
||||||
|
|
||||||
log(f"create_justificatif: {etud.id} {nouv_justificatif}")
|
log(f"create_justificatif: etudid={etud.id} {nouv_justificatif}")
|
||||||
Scolog.logdb(
|
Scolog.logdb(
|
||||||
method="create_justificatif",
|
method="create_justificatif",
|
||||||
etudid=etud.id,
|
etudid=etud.id,
|
||||||
@ -482,8 +482,6 @@ def compute_assiduites_justified(
|
|||||||
etudid: int, justificatifs: list[Justificatif] = None, reset: bool = False
|
etudid: int, justificatifs: list[Justificatif] = None, reset: bool = False
|
||||||
) -> list[int]:
|
) -> list[int]:
|
||||||
"""
|
"""
|
||||||
compute_assiduites_justified_faster
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
etudid (int): l'identifiant de l'étudiant
|
etudid (int): l'identifiant de l'étudiant
|
||||||
justificatifs (list[Justificatif]): La liste des justificatifs qui seront utilisés
|
justificatifs (list[Justificatif]): La liste des justificatifs qui seront utilisés
|
||||||
|
@ -122,26 +122,40 @@ def sidebar(etudid: int = None):
|
|||||||
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem)
|
nbabs, nbabsjust = sco_assiduites.get_assiduites_count(etudid, cur_sem)
|
||||||
nbabsnj = nbabs - nbabsjust
|
nbabsnj = nbabs - nbabsjust
|
||||||
H.append(
|
H.append(
|
||||||
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">({sco_preferences.get_preference("assi_metrique", None)})
|
f"""<span title="absences du { cur_sem["date_debut"] } au {
|
||||||
|
cur_sem["date_fin"] }">({
|
||||||
|
sco_preferences.get_preference("assi_metrique", None)})
|
||||||
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
|
||||||
)
|
)
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
if current_user.has_permission(Permission.AbsChange):
|
if current_user.has_permission(Permission.AbsChange):
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<li><a href="{ url_for('assiduites.ajout_assiduite_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Ajouter</a></li>
|
<li><a href="{ url_for('assiduites.ajout_assiduite_etud',
|
||||||
<li><a href="{ url_for('assiduites.ajout_justificatif_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Justifier</a></li>
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">Ajouter</a></li>
|
||||||
|
<li><a href="{ url_for('assiduites.ajout_justificatif_etud',
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">Justifier</a></li>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
if sco_preferences.get_preference("handle_billets_abs"):
|
if sco_preferences.get_preference("handle_billets_abs"):
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li><a href="{ url_for('absences.billets_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Billets</a></li>"""
|
f"""<li><a href="{ url_for('absences.billets_etud',
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">Billets</a></li>"""
|
||||||
)
|
)
|
||||||
H.append(
|
H.append(
|
||||||
f"""
|
f"""
|
||||||
<li><a href="{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Calendrier</a></li>
|
<li><a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||||
<li><a href="{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Liste</a></li>
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
<li><a href="{ url_for('assiduites.bilan_etud', scodoc_dept=g.scodoc_dept, etudid=etudid) }">Bilan</a></li>
|
}">Calendrier</a></li>
|
||||||
|
<li><a href="{ url_for('assiduites.liste_assiduites_etud',
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">Liste</a></li>
|
||||||
|
<li><a href="{ url_for('assiduites.bilan_etud',
|
||||||
|
scodoc_dept=g.scodoc_dept, etudid=etudid)
|
||||||
|
}">Bilan</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
@ -122,10 +122,10 @@ class JustificatifArchiver(BaseArchiver):
|
|||||||
archive_name: str = None,
|
archive_name: str = None,
|
||||||
description: str = "",
|
description: str = "",
|
||||||
user_id: str = None,
|
user_id: str = None,
|
||||||
) -> str:
|
) -> tuple[str, str]:
|
||||||
"""
|
"""
|
||||||
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
|
Ajoute un fichier dans une archive "justificatif" pour l'etudid donné
|
||||||
Retourne l'archive_name utilisé
|
Retourne l'archive_name utilisé et le filename
|
||||||
"""
|
"""
|
||||||
if archive_name is None:
|
if archive_name is None:
|
||||||
archive_id: str = self.create_obj_archive(
|
archive_id: str = self.create_obj_archive(
|
||||||
|
@ -316,7 +316,7 @@ class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
|||||||
# --- Absences
|
# --- Absences
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p>
|
f"""<p>
|
||||||
<a href="{ url_for('assiduites.calendrier_etud',
|
<a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||||
scodoc_dept=g.scodoc_dept, etudid=I['etudid']) }" class="bull_link">
|
scodoc_dept=g.scodoc_dept, etudid=I['etudid']) }" class="bull_link">
|
||||||
<b>Absences :</b>{I['nbabs']} demi-journées, dont {I['nbabsjust']} justifiées
|
<b>Absences :</b>{I['nbabs']} demi-journées, dont {I['nbabsjust']} justifiées
|
||||||
(pendant ce semestre).
|
(pendant ce semestre).
|
||||||
|
@ -134,8 +134,11 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
if nbabs:
|
if nbabs:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p class="bul_abs">
|
f"""<p class="bul_abs">
|
||||||
<a href="{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept, etudid=self.infos["etudid"]) }" class="bull_link">
|
<a href="{ url_for('assiduites.calendrier_assi_etud',
|
||||||
<b>Absences :</b> {self.infos['nbabs']} demi-journées, dont {self.infos['nbabsjust']} justifiées
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
etudid=self.infos["etudid"]) }" class="bull_link">
|
||||||
|
<b>Absences :</b> {self.infos['nbabs']} demi-journées, dont {
|
||||||
|
self.infos['nbabsjust']} justifiées
|
||||||
(pendant ce semestre).
|
(pendant ce semestre).
|
||||||
</a></p>
|
</a></p>
|
||||||
"""
|
"""
|
||||||
@ -186,7 +189,7 @@ class BulletinGeneratorStandard(sco_bulletins_generator.BulletinGenerator):
|
|||||||
if can_edit_app:
|
if can_edit_app:
|
||||||
H.append(
|
H.append(
|
||||||
f"""<p><a class="stdlink" href="{
|
f"""<p><a class="stdlink" href="{
|
||||||
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept,
|
url_for('notes.appreciation_add_form', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=self.etud.etudid,
|
etudid=self.etud.etudid,
|
||||||
formsemestre_id=self.formsemestre.id
|
formsemestre_id=self.formsemestre.id
|
||||||
)
|
)
|
||||||
|
@ -147,7 +147,7 @@ def evaluation_check_absences_html(
|
|||||||
H.append(': <span class="eval_check_absences_ok">ok</span>')
|
H.append(': <span class="eval_check_absences_ok">ok</span>')
|
||||||
H.append("</h2>")
|
H.append("</h2>")
|
||||||
|
|
||||||
def etudlist(etudids, linkabs=False):
|
def etudlist(etudids: list[int], linkabs=False):
|
||||||
H.append("<ul>")
|
H.append("<ul>")
|
||||||
if not etudids and show_ok:
|
if not etudids and show_ok:
|
||||||
H.append("<li>aucun</li>")
|
H.append("<li>aucun</li>")
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
:root {
|
:root {
|
||||||
--color-present: #6bdb83;
|
--color-present: #6bdb83;
|
||||||
--color-absent: #e62a11;
|
--color-absent: #e62a11;
|
||||||
--color-absent-clair: #f25d4a;
|
--color-absent-clair: rgb(252, 151, 50);
|
||||||
--color-retard: #f0c865;
|
--color-retard: #f0c865;
|
||||||
--color-justi: #7059ff;
|
--color-justi: #29b1b9;
|
||||||
--color-justi-clair: #6885e3;
|
--color-justi-clair: #48f6ff;
|
||||||
--color-justi-invalide: #a84476;
|
--color-justi-invalide: #a84476;
|
||||||
--color-nonwork: #badfff;
|
--color-nonwork: #badfff;
|
||||||
|
|
||||||
--color-absent-justi: #e65ab7;
|
--color-absent-justi: #ff86d7;
|
||||||
--color-retard-justi: #ffef7a;
|
--color-retard-justi: #e8c6eb;
|
||||||
|
|
||||||
--color-error: #e62a11;
|
--color-error: #e62a11;
|
||||||
--color-warning: #eec660;
|
--color-warning: #eec660;
|
||||||
@ -657,19 +657,35 @@ section.assi-form {
|
|||||||
table.liste_assi td.date {
|
table.liste_assi td.date {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
table.liste_assi.dataTable tbody td.date-debut {
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
table.liste_assi td.actions {
|
table.liste_assi td.actions {
|
||||||
white-space: nowrap; /* boutons horizontalement */
|
white-space: nowrap; /* boutons horizontalement */
|
||||||
}
|
}
|
||||||
|
table.liste_assi td.actions a:last-child {
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
tr.row-assiduite td {
|
tr.row-assiduite td {
|
||||||
border-bottom: 1px solid grey;
|
border-bottom: 1px solid grey;
|
||||||
}
|
}
|
||||||
|
table.liste_assi tbody tr td.assi-type {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
tr.row-assiduite.absent td.assi-type {
|
tr.row-assiduite.absent td.assi-type {
|
||||||
background-color: var(--color-absent-clair);
|
background-color: var(--color-absent-clair);
|
||||||
}
|
}
|
||||||
|
tr.row-assiduite.absent.justifiee td.assi-type {
|
||||||
|
background-color: var(--color-absent-justi);
|
||||||
|
}
|
||||||
tr.row-assiduite.retard td.assi-type {
|
tr.row-assiduite.retard td.assi-type {
|
||||||
background-color: var(--color-retard);
|
background-color: var(--color-retard);
|
||||||
}
|
}
|
||||||
tr.row-assiduite.present td.assi-type {
|
tr.row-assiduite.present td.assi-type {
|
||||||
background-color: var(--color-present);
|
background-color: var(--color-present);
|
||||||
}
|
}
|
||||||
|
tr.row-justificatif.valide td.assi-type {
|
||||||
|
background-color: var(--color-justi);
|
||||||
|
}
|
||||||
|
@ -247,7 +247,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
self.ligne["date_debut"].strftime("%d/%m/%y à %H:%M"),
|
self.ligne["date_debut"].strftime("%d/%m/%y à %H:%M"),
|
||||||
data={"order": self.ligne["date_debut"]},
|
data={"order": self.ligne["date_debut"]},
|
||||||
raw_content=self.ligne["date_debut"],
|
raw_content=self.ligne["date_debut"],
|
||||||
column_classes={"date"},
|
column_classes={"date", "date-debut"},
|
||||||
)
|
)
|
||||||
# Date de fin
|
# Date de fin
|
||||||
self.add_cell(
|
self.add_cell(
|
||||||
@ -256,7 +256,7 @@ class RowAssiJusti(tb.Row):
|
|||||||
self.ligne["date_fin"].strftime("%d/%m/%y à %H:%M"),
|
self.ligne["date_fin"].strftime("%d/%m/%y à %H:%M"),
|
||||||
raw_content=self.ligne["date_fin"],
|
raw_content=self.ligne["date_fin"],
|
||||||
data={"order": self.ligne["date_fin"]},
|
data={"order": self.ligne["date_fin"]},
|
||||||
column_classes={"date"},
|
column_classes={"date", "date-fin"},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ajout des colonnes optionnelles
|
# Ajout des colonnes optionnelles
|
||||||
@ -280,14 +280,16 @@ class RowAssiJusti(tb.Row):
|
|||||||
obj_type: str = ""
|
obj_type: str = ""
|
||||||
is_assiduite: bool = self.ligne["type"] == "assiduite"
|
is_assiduite: bool = self.ligne["type"] == "assiduite"
|
||||||
if is_assiduite:
|
if is_assiduite:
|
||||||
|
justifiee: str = "Justifiée" if self.ligne["est_just"] else ""
|
||||||
self.classes.append("row-assiduite")
|
self.classes.append("row-assiduite")
|
||||||
self.classes.append(EtatAssiduite(self.ligne["etat"]).name.lower())
|
self.classes.append(EtatAssiduite(self.ligne["etat"]).name.lower())
|
||||||
|
if self.ligne["est_just"]:
|
||||||
|
self.classes.append("justifiee")
|
||||||
etat: str = {
|
etat: str = {
|
||||||
EtatAssiduite.PRESENT: "Présence",
|
EtatAssiduite.PRESENT: "Présence",
|
||||||
EtatAssiduite.ABSENT: "Absence",
|
EtatAssiduite.ABSENT: "Absence",
|
||||||
EtatAssiduite.RETARD: "Retard",
|
EtatAssiduite.RETARD: "Retard",
|
||||||
}.get(self.ligne["etat"])
|
}.get(self.ligne["etat"])
|
||||||
justifiee: str = "Justifiée" if self.ligne["est_just"] else ""
|
|
||||||
obj_type = f"{etat} {justifiee}"
|
obj_type = f"{etat} {justifiee}"
|
||||||
else:
|
else:
|
||||||
self.classes.append("row-justificatif")
|
self.classes.append("row-justificatif")
|
||||||
@ -462,12 +464,12 @@ class AssiFiltre:
|
|||||||
type_filtrage, date = val_filtre
|
type_filtrage, date = val_filtre
|
||||||
|
|
||||||
match (type_filtrage):
|
match (type_filtrage):
|
||||||
# On garde uniquement les dates supérieur au filtre
|
# On garde uniquement les dates supérieures au filtre
|
||||||
case 2:
|
case 2:
|
||||||
query_filtree = query_filtree.filter(
|
query_filtree = query_filtree.filter(
|
||||||
getattr(obj_class, cle_filtre) > date
|
getattr(obj_class, cle_filtre) > date
|
||||||
)
|
)
|
||||||
# On garde uniquement les dates inférieur au filtre
|
# On garde uniquement les dates inférieures au filtre
|
||||||
case 1:
|
case 1:
|
||||||
query_filtree = query_filtree.filter(
|
query_filtree = query_filtree.filter(
|
||||||
getattr(obj_class, cle_filtre) < date
|
getattr(obj_class, cle_filtre) < date
|
||||||
|
@ -1,245 +0,0 @@
|
|||||||
{% include "assiduites/widgets/toast.j2" %}
|
|
||||||
{% block pageContent %}
|
|
||||||
<div class="pageContent">
|
|
||||||
<h3>Justifier des absences ou retards</h3>
|
|
||||||
|
|
||||||
<section class="justi-form page">
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_date_debut" required>Date de début</legend>
|
|
||||||
<scodoc-datetime name="justi_date_debut" id="justi_date_debut"> </scodoc-datetime>
|
|
||||||
<span>Journée entière</span> <input type="checkbox" name="justi_journee" id="justi_journee">
|
|
||||||
</div>
|
|
||||||
<div class="justi-label" id="date_fin">
|
|
||||||
<legend for="justi_date_fin" required>Date de fin</legend>
|
|
||||||
<scodoc-datetime name="justi_date_fin" id="justi_date_fin"></scodoc-datetime>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_etat" required>Etat du justificatif</legend>
|
|
||||||
<select name="justi_etat" id="justi_etat">
|
|
||||||
<option value="attente" selected>En Attente de validation</option>
|
|
||||||
<option value="non_valide">Non Valide</option>
|
|
||||||
<option value="modifie">Modifié</option>
|
|
||||||
<option value="valide">Valide</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_raison">Raison</legend>
|
|
||||||
<textarea name="justi_raison" id="justi_raison" cols="50" rows="10" maxlength="500"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="justi-row">
|
|
||||||
<div class="justi-sect">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="justi-label">
|
|
||||||
<legend for="justi_fich">Importer un fichier</legend>
|
|
||||||
<input type="file" name="justi_fich" id="justi_fich" multiple>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="justi-row">
|
|
||||||
<button onclick="validerFormulaire(this)">Créer le justificatif</button>
|
|
||||||
<button onclick="effacerFormulaire()">Remettre à zero</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section class="assi-liste">
|
|
||||||
{{tableau | safe }}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.justi-row {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justi-form fieldset {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageContent {
|
|
||||||
max-width: var(--sco-content-max-width);
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justi-label {
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[required]::after {
|
|
||||||
content: "*";
|
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
|
|
||||||
function validateFields() {
|
|
||||||
const field = document.querySelector('.justi-form')
|
|
||||||
const { deb, fin } = getDates()
|
|
||||||
const date_debut = new Date(deb);
|
|
||||||
const date_fin = new Date(fin);
|
|
||||||
|
|
||||||
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
|
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date_fin.isBefore(date_debut)) {
|
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldsToJustificatif() {
|
|
||||||
const field = document.querySelector('.justi-form.page')
|
|
||||||
|
|
||||||
const { deb, fin } = getDates()
|
|
||||||
|
|
||||||
const etat = field.querySelector('#justi_etat').value;
|
|
||||||
const raison = field.querySelector('#justi_raison').value;
|
|
||||||
|
|
||||||
return {
|
|
||||||
date_debut: new Date(deb).toFakeIso(),
|
|
||||||
date_fin: new Date(fin).toFakeIso(),
|
|
||||||
etat: etat,
|
|
||||||
raison: raison,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function importFiles(justif_id) {
|
|
||||||
const field = document.querySelector('.justi-form')
|
|
||||||
|
|
||||||
const in_files = field.querySelector('#justi_fich');
|
|
||||||
const path = getUrl() + `/api/justificatif/${justif_id}/import`;
|
|
||||||
|
|
||||||
const requests = []
|
|
||||||
Array.from(in_files.files).forEach((f) => {
|
|
||||||
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} commencée`), color = "var(--color-information)"));
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('file', f);
|
|
||||||
requests.push(
|
|
||||||
$.ajax(
|
|
||||||
{
|
|
||||||
url: path,
|
|
||||||
type: 'POST',
|
|
||||||
data: fd,
|
|
||||||
dateType: 'json',
|
|
||||||
contentType: false,
|
|
||||||
processData: false,
|
|
||||||
success: () => {
|
|
||||||
pushToast(generateToast(document.createTextNode(`Importation du fichier : ${f.name} finie`)));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
});
|
|
||||||
$.when(...requests).done(() => {
|
|
||||||
location.reload();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function validerFormulaire(btn) {
|
|
||||||
if (!validateFields()) return
|
|
||||||
|
|
||||||
const justificatif = fieldsToJustificatif();
|
|
||||||
let justif_id = null;
|
|
||||||
let couverture = null;
|
|
||||||
|
|
||||||
createJustificatif(justificatif, (data) => {
|
|
||||||
if (Object.keys(data.errors).length > 0) {
|
|
||||||
console.error(data.errors);
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
if (Object.keys(data.success).length > 0) {
|
|
||||||
couverture = data.success[0].message.couverture
|
|
||||||
justif_id = data.success[0].message.justif_id;
|
|
||||||
importFiles(justif_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
btn.disabled = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function effacerFormulaire() {
|
|
||||||
const field = document.querySelector('.justi-form')
|
|
||||||
|
|
||||||
field.querySelector('#justi_date_debut').value = "";
|
|
||||||
field.querySelector('#justi_date_fin').value = "";
|
|
||||||
field.querySelector('#justi_etat').value = "attente";
|
|
||||||
field.querySelector('#justi_raison').value = "";
|
|
||||||
field.querySelector('#justi_fich').value = "";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function dayOnly() {
|
|
||||||
const date_deb = document.getElementById("justi_date_debut");
|
|
||||||
const date_fin = document.getElementById("justi_date_fin");
|
|
||||||
|
|
||||||
if (document.getElementById('justi_journee').checked) {
|
|
||||||
date_deb.setAttribute("show", "date")
|
|
||||||
date_fin.setAttribute("show", "date")
|
|
||||||
document.querySelector(`legend[for="justi_date_fin"]`).removeAttribute("required")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
date_deb.removeAttribute("show")
|
|
||||||
date_fin.removeAttribute("show")
|
|
||||||
document.querySelector(`legend[for="justi_date_fin"]`).setAttribute("required", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDates() {
|
|
||||||
const date_deb = document.querySelector(".page #justi_date_debut")
|
|
||||||
const date_fin = document.querySelector(".page #justi_date_fin")
|
|
||||||
const journee = document.querySelector('.page #justi_journee').checked
|
|
||||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
|
||||||
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
|
||||||
if (journee) {
|
|
||||||
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
|
||||||
} else {
|
|
||||||
fin = date_fin.valueAsObject.date + fin
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
"deb": deb,
|
|
||||||
"fin": fin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const etudid = {{ sco.etud.id }};
|
|
||||||
|
|
||||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
|
||||||
const assi_morning = '{{assi_morning}}';
|
|
||||||
const assi_evening = '{{assi_evening}}';
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
|
|
||||||
dayOnly()
|
|
||||||
|
|
||||||
document.getElementById("justi_date_debut").valueAsObject = { time: assi_morning }
|
|
||||||
document.getElementById("justi_date_fin").valueAsObject = { time: assi_evening }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock pageContent %}
|
|
105
app/templates/assiduites/pages/ajout_justificatif_etud.j2
Normal file
105
app/templates/assiduites/pages/ajout_justificatif_etud.j2
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{% extends "sco_page.j2" %}
|
||||||
|
{% import 'wtf.j2' as wtf %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
{{super()}}
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.css"/>
|
||||||
|
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<style>
|
||||||
|
form#ajout-justificatif-etud {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
form#ajout-justificatif-etud > div {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
div.submit {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
div.submit > input {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="tab-content">
|
||||||
|
<h2>Justifier des absences ou retards</h2>
|
||||||
|
|
||||||
|
<section class="justi-form page">
|
||||||
|
|
||||||
|
<form id="ajout-justificatif-etud" method="post">
|
||||||
|
<fieldset>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{# Dates et heures #}
|
||||||
|
<div class="dates-heures">
|
||||||
|
{{ form.date_debut.label }} : {{ form.date_debut }}
|
||||||
|
de {{ form.heure_debut }} à {{ form.heure_fin }}
|
||||||
|
<span class="help">laisser les heures vides pour indiquer la journée entière</span>
|
||||||
|
{{ render_field_errors(form, 'date_debut') }}
|
||||||
|
{{ render_field_errors(form, 'heure_debut') }}
|
||||||
|
{{ render_field_errors(form, 'heure_fin') }}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{{ form.date_fin.label }} : {{ form.date_fin }}
|
||||||
|
<span class="help">si le jour de fin est différent,
|
||||||
|
les heures seront ignorées (journées complètes)</span>
|
||||||
|
{{ render_field_errors(form, 'date_fin') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{# Etat #}
|
||||||
|
<div>
|
||||||
|
<div>{{ form.etat.label }}</div>
|
||||||
|
{{ form.etat() }}
|
||||||
|
{{ render_field_errors(form, 'etat') }}
|
||||||
|
</div>
|
||||||
|
{# Raison #}
|
||||||
|
<div>
|
||||||
|
<div>{{ form.assi_raison.label }}</div>
|
||||||
|
{{ form.assi_raison() }}
|
||||||
|
{{ render_field_errors(form, 'assi_raison') }}
|
||||||
|
</div>
|
||||||
|
{# Fichier(s) justificatif(s) #}
|
||||||
|
<div>
|
||||||
|
<div>{{ form.fichiers.label }}</div>
|
||||||
|
{{ form.fichiers() }}
|
||||||
|
{{ render_field_errors(form, 'fichiers') }}
|
||||||
|
</div>
|
||||||
|
{# Date dépot #}
|
||||||
|
{{ form.entry_date.label }} : {{ form.entry_date }}
|
||||||
|
<span class="help">laisser vide pour date courante</span>
|
||||||
|
{{ render_field_errors(form, 'entry_date') }}
|
||||||
|
{# Submit #}
|
||||||
|
<div class="submit">
|
||||||
|
{{ form.submit }} {{ form.cancel }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section class="assi-liste">
|
||||||
|
{{tableau | safe }}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock app_content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{ super() }}
|
||||||
|
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/assiduites.js"></script>
|
||||||
|
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||||
|
<script>
|
||||||
|
$('.timepicker').timepicker({
|
||||||
|
timeFormat: 'HH:mm',
|
||||||
|
interval: {{ scu.get_assiduites_time_config("assi_tick_time") }},
|
||||||
|
minTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
maxTime: "{{ scu.get_assiduites_time_config("assi_afternoon_time") }}",
|
||||||
|
startTime: "{{ scu.get_assiduites_time_config("assi_morning_time") }}",
|
||||||
|
dynamic: false,
|
||||||
|
dropdown: true,
|
||||||
|
scrollbar: false
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock scripts %}
|
@ -1,5 +1,5 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="sco_box_title">Évènements enregistrés pour cet étudiant</div>
|
<div class="sco_box_title">{{ titre }}</div>
|
||||||
<div id="options-tableau">
|
<div id="options-tableau">
|
||||||
{% if afficher_options != false %}
|
{% if afficher_options != false %}
|
||||||
<input type="checkbox" id="show_pres" name="show_pres"
|
<input type="checkbox" id="show_pres" name="show_pres"
|
||||||
|
@ -71,7 +71,7 @@
|
|||||||
etudid=sco.etud.id) }}">Billets</a></li>
|
etudid=sco.etud.id) }}">Billets</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li><a href="{{ url_for('assiduites.calendrier_etud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('assiduites.calendrier_assi_etud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Calendrier</a></li>
|
etudid=sco.etud.id) }}">Calendrier</a></li>
|
||||||
<li><a href="{{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept,
|
<li><a href="{{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept,
|
||||||
etudid=sco.etud.id) }}">Liste</a></li>
|
etudid=sco.etud.id) }}">Liste</a></li>
|
||||||
|
@ -32,14 +32,18 @@ from flask import g, request, render_template, flash
|
|||||||
from flask import abort, url_for, redirect, Response
|
from flask import abort, url_for, redirect, Response
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
|
||||||
from app import db
|
from app import db, log
|
||||||
from app.comp import res_sem
|
from app.comp import res_sem
|
||||||
from app.comp.res_compat import NotesTableCompat
|
from app.comp.res_compat import NotesTableCompat
|
||||||
from app.decorators import (
|
from app.decorators import (
|
||||||
scodoc,
|
scodoc,
|
||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
from app.forms.assiduite.ajout_assiduite_etud import AjoutAssiduiteEtudForm
|
from app.forms.assiduite.ajout_assiduite_etud import (
|
||||||
|
AjoutAssiOrJustForm,
|
||||||
|
AjoutAssiduiteEtudForm,
|
||||||
|
AjoutJustificatifEtudForm,
|
||||||
|
)
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Assiduite,
|
Assiduite,
|
||||||
Departement,
|
Departement,
|
||||||
@ -53,7 +57,7 @@ from app.models import (
|
|||||||
)
|
)
|
||||||
from app.scodoc.codes_cursus import UE_STANDARD
|
from app.scodoc.codes_cursus import UE_STANDARD
|
||||||
from app.auth.models import User
|
from app.auth.models import User
|
||||||
from app.models.assiduites import get_assiduites_justif
|
from app.models.assiduites import get_assiduites_justif, compute_assiduites_justified
|
||||||
from app.tables.list_etuds import RowEtud, TableEtud
|
from app.tables.list_etuds import RowEtud, TableEtud
|
||||||
import app.tables.liste_assiduites as liste_assi
|
import app.tables.liste_assiduites as liste_assi
|
||||||
|
|
||||||
@ -63,7 +67,6 @@ from app.views import ScoData
|
|||||||
# ---------------
|
# ---------------
|
||||||
from app.scodoc.sco_permissions import Permission
|
from app.scodoc.sco_permissions import Permission
|
||||||
from app.scodoc import html_sco_header
|
from app.scodoc import html_sco_header
|
||||||
from app.scodoc import safehtml
|
|
||||||
from app.scodoc import sco_moduleimpl
|
from app.scodoc import sco_moduleimpl
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
from app.scodoc import sco_groups_view
|
from app.scodoc import sco_groups_view
|
||||||
@ -254,7 +257,7 @@ def bilan_dept():
|
|||||||
@bp.route("/ajout_assiduite_etud", methods=["GET", "POST"])
|
@bp.route("/ajout_assiduite_etud", methods=["GET", "POST"])
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
def ajout_assiduite_etud():
|
def ajout_assiduite_etud() -> str:
|
||||||
"""
|
"""
|
||||||
ajout_assiduite_etud Saisie d'une assiduité d'un étudiant
|
ajout_assiduite_etud Saisie d'une assiduité d'un étudiant
|
||||||
|
|
||||||
@ -315,12 +318,11 @@ def ajout_assiduite_etud():
|
|||||||
liste_assi.AssiJustifData.from_etudiants(
|
liste_assi.AssiJustifData.from_etudiants(
|
||||||
etud,
|
etud,
|
||||||
),
|
),
|
||||||
filename=f"assiduite-{etudid}",
|
filename=f"assiduite-{etud.nom or ''}",
|
||||||
afficher_etu=False,
|
afficher_etu=False,
|
||||||
filtre=liste_assi.AssiFiltre(type_obj=1),
|
filtre=liste_assi.AssiFiltre(type_obj=1),
|
||||||
options=liste_assi.AssiDisplayOptions(show_module=True),
|
options=liste_assi.AssiDisplayOptions(show_module=True),
|
||||||
)
|
)
|
||||||
#
|
|
||||||
if not is_html:
|
if not is_html:
|
||||||
return tableau
|
return tableau
|
||||||
|
|
||||||
@ -336,16 +338,13 @@ def ajout_assiduite_etud():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _record_assiduite_etud(
|
def _get_dates_from_assi_form(
|
||||||
etud: Identite,
|
form: AjoutAssiOrJustForm,
|
||||||
form: AjoutAssiduiteEtudForm,
|
) -> tuple[bool, datetime.datetime, datetime.datetime, datetime.datetime]:
|
||||||
) -> bool:
|
"""Prend les dates et heures du form, les vérifie
|
||||||
"""Enregistre les données du formulaire de saisie assiduité.
|
puis converti en deux datetime, en timezone du serveur.
|
||||||
Returns ok if successfully recorded, else put error info in the form.
|
Ramène ok=True si ok.
|
||||||
Format attendu des données du formulaire:
|
Met des messages d'erreur dans le form.
|
||||||
form.assi_etat.data : 'absent'
|
|
||||||
form.date_debut.data : '05/12/2023'
|
|
||||||
form.heure_debut.data : '09:06' (heure locale du serveur)
|
|
||||||
"""
|
"""
|
||||||
ok = True
|
ok = True
|
||||||
debut_jour = "00:00"
|
debut_jour = "00:00"
|
||||||
@ -384,6 +383,44 @@ def _record_assiduite_etud(
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
form.heure_fin.errors.append("heure fin invalide")
|
form.heure_fin.errors.append("heure fin invalide")
|
||||||
ok = False
|
ok = False
|
||||||
|
# Vérifie cohérence des dates/heures
|
||||||
|
dt_debut = datetime.datetime.combine(date_debut, heure_debut)
|
||||||
|
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
||||||
|
if dt_fin <= dt_debut:
|
||||||
|
form.errors["general_errors"] = ["Erreur: dates début/fin incohérentes"]
|
||||||
|
return False
|
||||||
|
# La date de dépot (si vide, la date actuelle)
|
||||||
|
dt_entry_date = (
|
||||||
|
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
|
||||||
|
if form.entry_date.data
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
# Ajoute time zone serveur
|
||||||
|
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
||||||
|
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
||||||
|
dt_entry_date_tz_server = (
|
||||||
|
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
||||||
|
)
|
||||||
|
return ok, dt_debut_tz_server, dt_fin_tz_server, dt_entry_date_tz_server
|
||||||
|
|
||||||
|
|
||||||
|
def _record_assiduite_etud(
|
||||||
|
etud: Identite,
|
||||||
|
form: AjoutAssiduiteEtudForm,
|
||||||
|
) -> bool:
|
||||||
|
"""Enregistre les données du formulaire de saisie assiduité.
|
||||||
|
Returns ok if successfully recorded, else put error info in the form.
|
||||||
|
Format attendu des données du formulaire:
|
||||||
|
form.assi_etat.data : 'absent'
|
||||||
|
form.date_debut.data : '05/12/2023'
|
||||||
|
form.heure_debut.data : '09:06' (heure locale du serveur)
|
||||||
|
"""
|
||||||
|
(
|
||||||
|
ok,
|
||||||
|
dt_debut_tz_server,
|
||||||
|
dt_fin_tz_server,
|
||||||
|
dt_entry_date_tz_server,
|
||||||
|
) = _get_dates_from_assi_form(form)
|
||||||
# Le module (avec "autre")
|
# Le module (avec "autre")
|
||||||
mod_data = form.modimpl.data
|
mod_data = form.modimpl.data
|
||||||
if mod_data:
|
if mod_data:
|
||||||
@ -397,26 +434,10 @@ def _record_assiduite_etud(
|
|||||||
ok = False
|
ok = False
|
||||||
else:
|
else:
|
||||||
moduleimpl_id = None
|
moduleimpl_id = None
|
||||||
# La date de dépot (si viden date actuelle)
|
|
||||||
dt_entry_date = (
|
|
||||||
datetime.datetime.strptime(form.entry_date.data, "%d/%m/%Y")
|
|
||||||
if form.entry_date.data
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
if not ok:
|
if not ok:
|
||||||
return False
|
return False
|
||||||
# Vérifie cohérence des dates/heures
|
|
||||||
dt_debut = datetime.datetime.combine(date_debut, heure_debut)
|
|
||||||
dt_fin = datetime.datetime.combine(date_fin or date_debut, heure_fin)
|
|
||||||
if dt_fin <= dt_debut:
|
|
||||||
form.errors["general_errors"] = ["Erreur: dates début/fin incohérentes"]
|
|
||||||
return False
|
|
||||||
# Ajoute time zone serveur
|
|
||||||
dt_debut_tz_server = scu.TIME_ZONE.localize(dt_debut)
|
|
||||||
dt_fin_tz_server = scu.TIME_ZONE.localize(dt_fin)
|
|
||||||
dt_entry_date_tz_server = (
|
|
||||||
scu.TIME_ZONE.localize(dt_entry_date) if dt_entry_date else None
|
|
||||||
)
|
|
||||||
external_data = None
|
external_data = None
|
||||||
moduleimpl: ModuleImpl | None = None
|
moduleimpl: ModuleImpl | None = None
|
||||||
match moduleimpl_id:
|
match moduleimpl_id:
|
||||||
@ -446,27 +467,6 @@ def _record_assiduite_etud(
|
|||||||
form.error_message = f"Erreur: {exc.args[0]}"
|
form.error_message = f"Erreur: {exc.args[0]}"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# # Génération de la page
|
|
||||||
# return HTMLBuilder(
|
|
||||||
# header,
|
|
||||||
# _mini_timeline(),
|
|
||||||
# render_template(
|
|
||||||
# "assiduites/pages/ajout_assiduites.j2",
|
|
||||||
# sco=ScoData(etud),
|
|
||||||
# assi_limit_annee=sco_preferences.get_preference(
|
|
||||||
# "assi_limit_annee",
|
|
||||||
# dept_id=g.scodoc_dept_id,
|
|
||||||
# ),
|
|
||||||
# saisie_eval=saisie_eval,
|
|
||||||
# date_deb=date_deb,
|
|
||||||
# date_fin=date_fin,
|
|
||||||
# etud=etud,
|
|
||||||
# redirect_url=redirect_url,
|
|
||||||
# moduleimpl_id=moduleimpl_id,
|
|
||||||
# tableau=tableau,
|
|
||||||
# scu=scu,
|
|
||||||
# ),
|
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/liste_assiduites_etud")
|
@bp.route("/liste_assiduites_etud")
|
||||||
@scodoc
|
@scodoc
|
||||||
@ -585,7 +585,9 @@ def bilan_etud():
|
|||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/AjoutJustificatifEtud")
|
@bp.route(
|
||||||
|
"/ajout_justificatif_etud", methods=["GET", "POST"]
|
||||||
|
) # was AjoutJustificatifEtud
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.AbsChange)
|
@permission_required(Permission.AbsChange)
|
||||||
def ajout_justificatif_etud():
|
def ajout_justificatif_etud():
|
||||||
@ -597,63 +599,130 @@ def ajout_justificatif_etud():
|
|||||||
Returns:
|
Returns:
|
||||||
str: l'html généré
|
str: l'html généré
|
||||||
"""
|
"""
|
||||||
|
etud = Identite.get_etud(request.args.get("etudid"))
|
||||||
# Récupération de l'étudiant concerné
|
redirect_url = url_for(
|
||||||
etudid = request.args.get("etudid", -1)
|
"assiduites.calendrier_assi_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
etud: Identite = Identite.query.get_or_404(etudid)
|
|
||||||
if etud.dept_id != g.scodoc_dept_id:
|
|
||||||
abort(404, "étudiant inexistant dans ce département")
|
|
||||||
|
|
||||||
# Préparation de la page (header)
|
|
||||||
header: str = html_sco_header.sco_header(
|
|
||||||
page_title="Justificatifs",
|
|
||||||
init_qtip=True,
|
|
||||||
javascripts=[
|
|
||||||
"js/assiduites.js",
|
|
||||||
"js/date_utils.js",
|
|
||||||
],
|
|
||||||
cssstyles=CSSSTYLES
|
|
||||||
+ [
|
|
||||||
"css/assiduites.css",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tableau = _prepare_tableau(
|
form = AjoutJustificatifEtudForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if form.cancel.data: # cancel button
|
||||||
|
return redirect(redirect_url)
|
||||||
|
ok = _record_justificatif_etud(etud, form)
|
||||||
|
if ok:
|
||||||
|
return redirect(redirect_url)
|
||||||
|
|
||||||
|
is_html, tableau = _prepare_tableau(
|
||||||
liste_assi.AssiJustifData.from_etudiants(
|
liste_assi.AssiJustifData.from_etudiants(
|
||||||
etud,
|
etud,
|
||||||
),
|
),
|
||||||
filename=f"justificatifs-{etudid}",
|
filename=f"justificatifs-{etud.nom or ''}",
|
||||||
afficher_etu=False,
|
afficher_etu=False,
|
||||||
filtre=liste_assi.AssiFiltre(type_obj=2),
|
filtre=liste_assi.AssiFiltre(type_obj=2),
|
||||||
options=liste_assi.AssiDisplayOptions(show_module=False, show_desc=True),
|
options=liste_assi.AssiDisplayOptions(show_module=False, show_desc=True),
|
||||||
afficher_options=False,
|
afficher_options=False,
|
||||||
|
titre="Justificatifs enregistrés pour cet étudiant",
|
||||||
)
|
)
|
||||||
if not tableau[0]:
|
if not is_html:
|
||||||
return tableau[1]
|
return tableau
|
||||||
|
|
||||||
# Peuplement du template jinja
|
return render_template(
|
||||||
return HTMLBuilder(
|
"assiduites/pages/ajout_justificatif_etud.j2",
|
||||||
header,
|
assi_limit_annee=sco_preferences.get_preference(
|
||||||
render_template(
|
"assi_limit_annee",
|
||||||
"assiduites/pages/ajout_justificatif.j2",
|
dept_id=g.scodoc_dept_id,
|
||||||
sco=ScoData(etud),
|
|
||||||
assi_limit_annee=sco_preferences.get_preference(
|
|
||||||
"assi_limit_annee",
|
|
||||||
dept_id=g.scodoc_dept_id,
|
|
||||||
),
|
|
||||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
|
||||||
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
|
||||||
tableau=tableau[1],
|
|
||||||
),
|
),
|
||||||
).build()
|
etud=etud,
|
||||||
|
form=form,
|
||||||
|
page_title="Justificatifs",
|
||||||
|
redirect_url=redirect_url,
|
||||||
|
sco=ScoData(etud),
|
||||||
|
scu=scu,
|
||||||
|
tableau=tableau,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/CalendrierAssiduitesEtud")
|
def _record_justificatif_etud(
|
||||||
|
etud: Identite,
|
||||||
|
form: AjoutJustificatifEtudForm,
|
||||||
|
) -> bool:
|
||||||
|
"""Enregistre les données du formulaire de saisie justificatif (et ses fichiers).
|
||||||
|
Returns ok if successfully recorded, else put error info in the form.
|
||||||
|
Format attendu des données du formulaire:
|
||||||
|
form.assi_etat.data : 'absent'
|
||||||
|
form.date_debut.data : '05/12/2023'
|
||||||
|
form.heure_debut.data : '09:06' (heure locale du serveur)
|
||||||
|
"""
|
||||||
|
(
|
||||||
|
ok,
|
||||||
|
dt_debut_tz_server,
|
||||||
|
dt_fin_tz_server,
|
||||||
|
dt_entry_date_tz_server,
|
||||||
|
) = _get_dates_from_assi_form(form)
|
||||||
|
|
||||||
|
if not ok:
|
||||||
|
return False
|
||||||
|
etat = scu.EtatJustificatif.get(form.etat.data)
|
||||||
|
try:
|
||||||
|
just = Justificatif.create_justificatif(
|
||||||
|
etud,
|
||||||
|
dt_debut_tz_server,
|
||||||
|
dt_fin_tz_server,
|
||||||
|
etat=etat,
|
||||||
|
raison=form.assi_raison.data,
|
||||||
|
entry_date=dt_entry_date_tz_server,
|
||||||
|
user_id=current_user.id,
|
||||||
|
)
|
||||||
|
db.session.add(just)
|
||||||
|
if not _upload_justificatif_files(just, form):
|
||||||
|
db.session.rollback()
|
||||||
|
return False
|
||||||
|
db.session.commit()
|
||||||
|
compute_assiduites_justified(etud.id, [just])
|
||||||
|
flash("Justificatif enregistré")
|
||||||
|
return True
|
||||||
|
except ScoValueError as exc:
|
||||||
|
db.session.rollback()
|
||||||
|
form.error_message = f"Erreur: {exc.args[0]}"
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _upload_justificatif_files(
|
||||||
|
just: Justificatif, form: AjoutJustificatifEtudForm
|
||||||
|
) -> bool:
|
||||||
|
"""Enregistre les fichiers du formulaire de création de justificatif"""
|
||||||
|
# Utilisation de l'archiver de justificatifs
|
||||||
|
archiver: JustificatifArchiver = JustificatifArchiver()
|
||||||
|
archive_name: str = just.fichier
|
||||||
|
try:
|
||||||
|
# On essaye de sauvegarder les fichiers
|
||||||
|
for file in form.fichiers.data or []:
|
||||||
|
archive_name, _ = archiver.save_justificatif(
|
||||||
|
just.etudiant,
|
||||||
|
filename=file.filename,
|
||||||
|
data=file.stream.read(),
|
||||||
|
archive_name=archive_name,
|
||||||
|
user_id=current_user.id,
|
||||||
|
)
|
||||||
|
flash(f"Fichier {file.filename} enregistré")
|
||||||
|
if form.fichiers.data:
|
||||||
|
# On actualise l'archive du justificatif
|
||||||
|
just.fichier = archive_name
|
||||||
|
db.session.add(just)
|
||||||
|
|
||||||
|
return True
|
||||||
|
except ScoValueError as exc:
|
||||||
|
log(f"_upload_justificatif_files: error on {file.filename} for etud {etud.id}")
|
||||||
|
form.error_message = f"Erreur sur fichier justificatif: {exc.args[0]}"
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/calendrier_assi_etud")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoView)
|
@permission_required(Permission.ScoView)
|
||||||
def calendrier_etud():
|
def calendrier_assi_etud():
|
||||||
"""
|
"""
|
||||||
calendrier_etud : Affichage d'un calendrier des assiduités de l'étudiant
|
Affichage d'un calendrier de l'assiduité de l'étudiant
|
||||||
Args:
|
Args:
|
||||||
etudid (int): l'identifiant de l'étudiant
|
etudid (int): l'identifiant de l'étudiant
|
||||||
|
|
||||||
@ -1028,7 +1097,7 @@ class RowEtudWithAssi(RowEtud):
|
|||||||
self.etat_assiduite = etat_assiduite
|
self.etat_assiduite = etat_assiduite
|
||||||
# remplace lien vers fiche par lien vers calendrier
|
# remplace lien vers fiche par lien vers calendrier
|
||||||
self.target_url = url_for(
|
self.target_url = url_for(
|
||||||
"assiduites.calendrier_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
"assiduites.calendrier_assi_etud", scodoc_dept=g.scodoc_dept, etudid=etud.id
|
||||||
)
|
)
|
||||||
self.target_title = f"Calendrier de {etud.nomprenom}"
|
self.target_title = f"Calendrier de {etud.nomprenom}"
|
||||||
|
|
||||||
@ -1206,6 +1275,7 @@ def _prepare_tableau(
|
|||||||
filtre: liste_assi.AssiFiltre = None,
|
filtre: liste_assi.AssiFiltre = None,
|
||||||
options: liste_assi.AssiDisplayOptions = None,
|
options: liste_assi.AssiDisplayOptions = None,
|
||||||
afficher_options: bool = True,
|
afficher_options: bool = True,
|
||||||
|
titre="Évènements enregistrés pour cet étudiant",
|
||||||
) -> tuple[bool, Response | str]:
|
) -> tuple[bool, Response | str]:
|
||||||
"""
|
"""
|
||||||
Prépare un tableau d'assiduités / justificatifs
|
Prépare un tableau d'assiduités / justificatifs
|
||||||
@ -1274,6 +1344,7 @@ def _prepare_tableau(
|
|||||||
total_pages=table.total_pages,
|
total_pages=table.total_pages,
|
||||||
options=options,
|
options=options,
|
||||||
afficher_options=afficher_options,
|
afficher_options=afficher_options,
|
||||||
|
titre=titre,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.67"
|
SCOVERSION = "9.6.68"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user