diff --git a/app/models/assiduites.py b/app/models/assiduites.py
index db7a2587..745dfdbb 100644
--- a/app/models/assiduites.py
+++ b/app/models/assiduites.py
@@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*
-"""Gestion de l'assiduité (assiduités + justificatifs)
-"""
+"""Gestion de l'assiduité (assiduités + justificatifs)"""
+
from datetime import datetime
from flask_login import current_user
@@ -336,13 +336,19 @@ class Assiduite(ScoDocModel):
"""
return get_formsemestre_from_data(self.to_dict())
- def get_module(self, traduire: bool = False) -> int | str:
- "TODO documenter"
+ def get_module(self, traduire: bool = False) -> Module | str:
+ """
+ Retourne le module associé à l'assiduité
+ Si traduire est vrai, retourne le titre du module précédé du code
+ Sinon rentourne l'objet Module ou None
+ """
+
if self.moduleimpl_id is not None:
+ modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
+ mod: Module = Module.query.get(modimpl.module_id)
if traduire:
- modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
- mod: Module = Module.query.get(modimpl.module_id)
return f"{mod.code} {mod.titre}"
+ return mod
elif self.external_data is not None and "module" in self.external_data:
return (
diff --git a/app/tables/liste_assiduites.py b/app/tables/liste_assiduites.py
index 2441f101..819c3342 100644
--- a/app/tables/liste_assiduites.py
+++ b/app/tables/liste_assiduites.py
@@ -12,7 +12,7 @@ from sqlalchemy import desc, literal, union, asc
from app import db, g
from app.auth.models import User
-from app.models import Assiduite, Identite, Justificatif
+from app.models import Assiduite, Identite, Justificatif, Module
from app.scodoc.sco_utils import (
EtatAssiduite,
EtatJustificatif,
@@ -534,10 +534,45 @@ class RowAssiJusti(tb.Row):
if self.table.options.show_module:
if self.ligne["type"] == "assiduite":
assi: Assiduite = Assiduite.query.get(self.ligne["obj_id"])
- mod: str = assi.get_module(True)
- self.add_cell("module", "Module", mod, data={"order": mod})
+ if self.table.no_pagination:
+ mod: Module = assi.get_module(False)
+ code = mod.code if isinstance(mod, Module) else ""
+ titre = ""
+ if isinstance(mod, Module):
+ titre = mod.titre
+ elif isinstance(mod, str):
+ titre = mod
+ else:
+ titre = "Non Spécifié"
+
+ self.add_cell(
+ "code_module", "Code Module", code, data={"order": code}
+ )
+ self.add_cell(
+ "titre_module",
+ "Titre Module",
+ titre,
+ data={"order": titre},
+ )
+ else:
+ mod: Module = assi.get_module(True)
+ self.add_cell(
+ "module",
+ "Module",
+ mod,
+ data={"order": mod},
+ )
else:
- self.add_cell("module", "Module", "", data={"order": ""})
+ if self.table.no_pagination:
+ self.add_cell("module", "Module", "", data={"order": ""})
+ else:
+ self.add_cell("code_module", "Code Module", "", data={"order": ""})
+ self.add_cell(
+ "titre_module",
+ "Titre Module",
+ "",
+ data={"order": ""},
+ )
def _utilisateur(self) -> None:
utilisateur: User = (
diff --git a/app/templates/assiduites/pages/bilan_dept.j2 b/app/templates/assiduites/pages/bilan_dept.j2
index f4a20efb..4fa342eb 100644
--- a/app/templates/assiduites/pages/bilan_dept.j2
+++ b/app/templates/assiduites/pages/bilan_dept.j2
@@ -20,6 +20,39 @@ le semestre concerné (saisie par jour ou saisie différée).
{{billets | safe}}
+
+
+
+
Télécharger l'assiduité
+
+
+
+
+
{{tableau | safe }}
diff --git a/app/templates/assiduites/pages/signal_assiduites_diff.j2 b/app/templates/assiduites/pages/signal_assiduites_diff.j2
index f19e59ac..120369fe 100644
--- a/app/templates/assiduites/pages/signal_assiduites_diff.j2
+++ b/app/templates/assiduites/pages/signal_assiduites_diff.j2
@@ -17,6 +17,15 @@
gap: 0.5em;
}
+ #actions {
+ flex-direction: row;
+ align-items: center;
+ margin-bottom: 5px;
+ }
+ #actions label{
+ margin: 0;
+ }
+
#fix {
display: flex;
flex-direction: row;
@@ -49,21 +58,24 @@
}
#tableau-periode {
+ display: flex;
+ flex-direction: column;
overflow-x: scroll;
max-width: var(--sco-content-max-width);
}
#tableau-periode .pdp {
- width: 2em;
- height: 2em;
- border-radius: 50%;
+ width: 5em;
+ border-radius: 8px;
}
- .grid-table {
- display: grid;
- grid-template-columns: 200px repeat({{ etudiants|length }}, 1fr);
- width: var(--sco-content-max-width);
+ .header {
+ background-color: #f9f9f9;
+ padding: 10px;
+ text-align: center;
+ border: 1px solid #ddd;
}
+
.cell, .header {
border: 1px solid #ddd;
padding: 10px;
@@ -74,19 +86,23 @@
}
.header{
justify-content: space-between;
-
}
- .cell{
+
+ .cell {
+ display: flex;
+ align-items: center;
justify-content: center;
-
+ border: 1px solid #ddd;
+ padding: 10px;
+ text-align: center;
+ width: 256px;
}
-
.cell p{
text-align: center;
}
.sticky {
position: sticky;
- left: 0;
+ top: 0;
background-color: #f9f9f9;
z-index: 2;
}
@@ -95,12 +111,24 @@
display: block;
top: 0;
z-index: 0;
+ width: 100% !important;
+ min-width: inherit !important;
+ }
+
+ .assi-btns {
+ display: flex;
+ gap: 4px;
}
.pointer{
cursor: pointer;
}
+ .ligne{
+ display: flex;
+ gap: 1px;
+ }
+
{% endblock styles %}
@@ -124,6 +152,10 @@ function afficherPDP(checked) {
} else {
gtrcontent.removeAttribute("data-pdp");
}
+
+ // On sauvegarde le choix dans le localStorage
+ localStorage.setItem("scodoc-signal_assiduites_diff-pdp", `${checked}`);
+ pdp.checked = checked;
}
/**
@@ -156,7 +188,7 @@ async function nouvellePeriode(period = null) {
moduleimpl_id = period.moduleimpl_id;
}else{
//Sinon on vérifie qu'on a bien des valeurs
- const text = document.createTextNode("Veuillez remplir tous les champs pour ajouter une période.")
+ const text = document.createTextNode("Veuillez remplir tous les champs pour ajouter une plage.")
if (date == "" || debut == "" || fin == "" || moduleimpl_id == "") {
openAlertModal(
"Erreur",
@@ -168,10 +200,11 @@ async function nouvellePeriode(period = null) {
// On ajoute la nouvelle période au tableau
let periodeDiv = document.createElement("div");
- periodeDiv.classList.add("cell", "sticky");
+ periodeDiv.classList.add("cell", "header");
periodeDiv.id = `periode-${periodId}`;
+
const periodP = document.createElement("p");
- periodP.textContent = `Période du ${date} de ${debut} à ${fin}`;
+ periodP.textContent = `Plage du ${date} de ${debut} à ${fin}`;
// On ajoute le moduleimpl
const modP = document.createElement("p");
@@ -184,7 +217,7 @@ async function nouvellePeriode(period = null) {
// On supprime toutes les cases du tableau correspondant à cette période
document
.querySelectorAll(
- `.cell[data-periodeid="${periodeDiv.getAttribute("data-periodeid")}"]`
+ `[data-periodeid="${periodeDiv.getAttribute("data-periodeid")}"]`
)
.forEach((e) => e.remove());
// On supprime la période de la Map periodes
@@ -195,11 +228,11 @@ async function nouvellePeriode(period = null) {
periodeDiv.appendChild(modP);
periodeDiv.appendChild(close);
periodeDiv.setAttribute("data-periodeid", periodId);
- document.getElementById("tableau-periode").appendChild(periodeDiv);
+ document.getElementById("tete-table").appendChild(periodeDiv);
// On récupère les étudiants (etudids)
let etudids = [
- ...document.querySelectorAll("#tableau-periode .header[data-etudid]"),
+ ...document.querySelectorAll(".ligne[data-etudid]"),
].map((e) => e.getAttribute("data-etudid"));
// On génère une date de début et de fin de la période
@@ -249,7 +282,7 @@ async function nouvellePeriode(period = null) {
cell.setAttribute("data-etudid", etudid);
cell.setAttribute("data-periodeid", periodId);
cell.id = `cell-${etudid}-${periodId}`;
- document.getElementById("tableau-periode").appendChild(cell);
+ document.querySelector(`.ligne[data-etudid="${etudid}"]`).appendChild(cell);
//Vérification inscription au module
// Si l'étudiant n'est pas inscrit, on le notifie et on passe à l'étudiant suivant
@@ -265,6 +298,10 @@ async function nouvellePeriode(period = null) {
const assiduites = data[etudid];
// Si l'étudiant n'a pas d'assiduité, on crée les boutons assiduité
if (assiduites.length == 0) {
+
+ const assi_btns = document.createElement('div');
+ assi_btns.classList.add('assi-btns');
+
["present", "retard", "absent"].forEach((value) => {
const cbox = document.createElement("input");
cbox.type = "checkbox";
@@ -284,8 +321,9 @@ async function nouvellePeriode(period = null) {
// Si une valeur par défaut est donnée alors on l'applique
cbox.checked = etatDef.value == value;
- cell.appendChild(cbox);
+ assi_btns.appendChild(cbox);
});
+ cell.appendChild(assi_btns);
} else {
// Si une (ou plus) assiduité sont trouvée pour la période
// alors on affiche les informations de la première assiduité
@@ -297,6 +335,8 @@ async function nouvellePeriode(period = null) {
.catch((error) => {
console.error("Error:", error);
});
+
+ document.getElementById("tableau-periode").classList.remove("hidden");
}
/**
* Permet de récupérer la saisie puis créer les assiduités grâce à l'api
@@ -337,6 +377,7 @@ function sauvegarderAssiduites() {
}
});
}
+
// Une fois les assiduités générées, on les envoie à l'api
async_post(
"../../api/assiduites/create",
@@ -344,7 +385,7 @@ function sauvegarderAssiduites() {
// Si la requête passe
async (data) => {
// On supprime toutes les cases du tableau pour le mettre à jour
- document.querySelectorAll(".cell").forEach((e) => e.remove());
+ document.querySelectorAll("[data-periodeid]").forEach((e)=>e.remove())
// On recrée les périodes
// (cela permet de redemander les assiduités, donc mettre à jour les cases)
@@ -402,7 +443,7 @@ function sauvegarderAssiduites() {
const period = periodes.get(periodeId);
const li = document.createElement("li");
// On affiche la période
- li.textContent = `Période du ${period.date_debut.format(
+ li.textContent = `Plage du ${period.date_debut.format(
"DD/MM/YYYY HH:mm"
)} à ${period.date_fin.format("HH:mm")}`;
@@ -474,7 +515,8 @@ if (window.forceModule) {
* - On vérifie si la date est un jour travaillé
*/
async function main() {
- afficherPDP(pdp.checked);
+ const checked = localStorage.getItem("scodoc-signal_assiduites_diff-pdp") == "true";
+ afficherPDP(checked);
$("#date").on("change", async function (d) {
// On vérifie si la date est un jour travaillé
dateCouranteEstTravaillee();
@@ -497,8 +539,8 @@ main();
-
+
+
+
+
-
-
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
+ {# #}
+
+ {# ... #}
+
+
+
{% for etud in etudiants %}
-
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index 347b140c..cb0f8f92 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -186,6 +186,12 @@ def bilan_dept():
if not table[0]:
return table[1]
+ # Récupération des formsemestres (pour le menu déroulant)
+ formsemestres: Query = FormSemestre.get_dept_formsemestres_courants(dept)
+ formsemestres_choices: dict[int, str] = {
+ fs.id: fs.titre_annee() for fs in formsemestres
+ }
+
# Peuplement du template jinja
return render_template(
"assiduites/pages/bilan_dept.j2",
@@ -193,6 +199,8 @@ def bilan_dept():
search_etud=sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"),
billets=billets,
sco=ScoData(formsemestre=formsemestre),
+ formsemestres=formsemestres_choices,
+ formsemestre_id=None if not formsemestre else formsemestre.id,
)
@@ -1565,6 +1573,85 @@ def _prepare_tableau(
)
+@bp.route("/recup_assiduites_plage", methods=["POST"])
+@scodoc
+@permission_required(Permission.AbsChange)
+def recup_assiduites_plage():
+ """
+ Renvoie un fichier excel contenant toutes les assiduités d'une plage
+ La plage est définie par les valeurs "datedeb" et "datefin" du formulaire
+ Par défaut tous les étudiants du département sont concernés
+ Si le champs "formsemestre_id" est présent dans le formulaire et est non vide,
+ seuls les étudiants inscrits dans ce semestre sont concernés.
+ """
+
+ date_deb: datetime.datetime = request.form.get("datedeb")
+ date_fin: datetime.datetime = request.form.get("datefin")
+
+ # Vérification des dates
+ try:
+ date_deb = datetime.datetime.strptime(date_deb, "%d/%m/%Y")
+ except ValueError as exc:
+ raise ScoValueError("date_debut invalide", dest_url=request.referrer) from exc
+ try:
+ date_fin = datetime.datetime.strptime(date_fin, "%d/%m/%Y")
+ except ValueError as exc:
+ raise ScoValueError("date_fin invalide", dest_url=request.referrer) from exc
+
+ # Récupération des étudiants
+ etuds: Query = []
+ formsemestre_id: str | None = request.form.get("formsemestre_id")
+
+ name: str = ""
+
+ if formsemestre_id is not None and formsemestre_id != "":
+ formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
+ etuds = formsemestre.etuds
+ name = formsemestre.session_id()
+ else:
+ dept: Departement = Departement.query.get_or_404(g.scodoc_dept_id)
+ etuds = dept.etudiants
+ name = dept.acronym
+
+ # Récupération des assiduités
+ assiduites: Query = Assiduite.query.filter(
+ Assiduite.etudid.in_([etud.id for etud in etuds])
+ )
+
+ # Filtrage des assiduités en fonction des dates données
+ assiduites = scass.filter_by_date(assiduites, Assiduite, date_deb, date_fin)
+
+ table_data: liste_assi.AssiJustifData = liste_assi.AssiJustifData(
+ assiduites_query=assiduites,
+ )
+
+ options: liste_assi.AssiDisplayOptions = liste_assi.AssiDisplayOptions(
+ show_pres=True,
+ show_reta=True,
+ show_module=True,
+ show_etu=True,
+ )
+
+ date_deb_str: str = date_deb.strftime("%d-%m-%Y")
+ date_fin_str: str = date_fin.strftime("%d-%m-%Y")
+
+ filename: str = f"assiduites_{name}_{date_deb_str}_{date_fin_str}"
+
+ tableau: liste_assi.ListeAssiJusti = liste_assi.ListeAssiJusti(
+ table_data,
+ options=options,
+ titre="tableau-dept-" + filename,
+ no_pagination=True,
+ )
+
+ return scu.send_file(
+ tableau.excel(),
+ filename=filename,
+ mime=scu.XLSX_MIMETYPE,
+ suffix=scu.XLSX_SUFFIX,
+ )
+
+
@bp.route("/tableau_assiduite_actions", methods=["GET", "POST"])
@scodoc
@permission_required(Permission.AbsChange)