diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py
index d1b7862d3..52ddee04e 100644
--- a/app/api/justificatifs.py
+++ b/app/api/justificatifs.py
@@ -18,7 +18,7 @@ from app.api import api_bp as bp
from app.api import api_web_bp
from app.api import get_model_api_object
from app.decorators import permission_required, scodoc
-from app.models import Identite, Justificatif
+from app.models import Identite, Justificatif, Departement
from app.models.assiduites import compute_assiduites_justified
from app.scodoc.sco_archives_justificatifs import JustificatifArchiver
from app.scodoc.sco_exceptions import ScoValueError
@@ -105,6 +105,31 @@ def justificatifs(etudid: int = None, with_query: bool = False):
return data_set
+@api_web_bp.route("/justificatifs/dept/", defaults={"with_query": False})
+@api_web_bp.route(
+ "/justificatifs/dept//query", defaults={"with_query": True}
+)
+@login_required
+@scodoc
+@as_json
+@permission_required(Permission.ScoView)
+def justificatifs_dept(dept_id: int = None, with_query : bool = False):
+ """ """
+ dept = Departement.query.get_or_404(dept_id)
+ etuds = [etud.id for etud in dept.etudiants]
+
+ justificatifs_query = Justificatif.query.filter(Justificatif.etudid.in_(etuds))
+
+ if with_query:
+ justificatifs_query = _filter_manager(request, justificatifs_query)
+ data_set: list[dict] = []
+ for just in justificatifs_query.all():
+ data = just.to_dict(format_api=True)
+ data_set.append(data)
+
+ return data_set
+
+
@bp.route("/justificatif//create", methods=["POST"])
@api_web_bp.route("/justificatif//create", methods=["POST"])
@scodoc
diff --git a/app/scodoc/html_sidebar.py b/app/scodoc/html_sidebar.py
index cb93b15e1..ad720791a 100755
--- a/app/scodoc/html_sidebar.py
+++ b/app/scodoc/html_sidebar.py
@@ -54,7 +54,7 @@ def sidebar_common():
-
+
"""
]
if current_user.has_permission(
@@ -138,6 +138,7 @@ def sidebar(etudid: int = None):
f"""
Calendrier
Liste
+ Bilan
"""
)
diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py
index 05167c664..ea5d29231 100755
--- a/app/scodoc/sco_formsemestre_status.py
+++ b/app/scodoc/sco_formsemestre_status.py
@@ -213,13 +213,14 @@ def formsemestre_status_menubar(formsemestre: FormSemestre) -> str:
"enabled": True,
"helpmsg": "",
},
- {
- "title": "Vérifier absences aux évaluations",
- "endpoint": "notes.formsemestre_check_absences_html",
- "args": {"formsemestre_id": formsemestre_id},
- "enabled": True,
- "helpmsg": "",
- },
+ # TODO: Mettre à jour avec module Assiduités
+ # {
+ # "title": "Vérifier absences aux évaluations",
+ # "endpoint": "notes.formsemestre_check_absences_html",
+ # "args": {"formsemestre_id": formsemestre_id},
+ # "enabled": True,
+ # "helpmsg": "",
+ # },
{
"title": "Lister tous les enseignants",
"endpoint": "notes.formsemestre_enseignants_list",
diff --git a/app/scodoc/sco_moduleimpl_status.py b/app/scodoc/sco_moduleimpl_status.py
index fefeacbef..56cbd4ab7 100644
--- a/app/scodoc/sco_moduleimpl_status.py
+++ b/app/scodoc/sco_moduleimpl_status.py
@@ -138,7 +138,7 @@ def moduleimpl_evaluation_menu(evaluation_id, nbnotes=0) -> str:
},
{
"title": "Absences ce jour",
- "endpoint": "absences.EtatAbsencesDate",
+ "endpoint": "assiduites.get_etat_abs_date",
"args": {
"group_ids": group_id,
"desc": E["description"],
diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py
index b7099dabb..b6a064ed7 100644
--- a/app/scodoc/sco_preferences.py
+++ b/app/scodoc/sco_preferences.py
@@ -661,7 +661,19 @@ class BasePreferences(object):
"labels": ["1/2 J.", "J.", "H."],
"allowed_values": ["1/2 J.", "J.", "H."],
"title": "Métrique de l'assiduité",
- "explanation": "Unité affichée dans la fiche étudiante et le bilan\n(J. = journée, H. = heure)",
+ "explanation": "Unité utilisée dans la fiche étudiante, le bilan, et dans les calculs (J. = journée, H. = heure)",
+ "category": "assi",
+ "only_global": True,
+ },
+ ),
+ (
+ "assi_seuil",
+ {
+ "initvalue": 3.0,
+ "size": 10,
+ "title": "Seuil d'alerte des absences",
+ "type": "float",
+ "explanation": "Nombres d'absences limite avant alerte dans le bilan (utilisation de l'unité métrique ↑ )",
"category": "assi",
"only_global": True,
},
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 73e056995..c163806ee 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -619,6 +619,13 @@ def AbsencesURL():
]
+def AssiduitesURL():
+ """URL of Assiduités"""
+ return url_for("assiduites.index_html", scodoc_dept=g.scodoc_dept)[
+ : -len("/index_html")
+ ]
+
+
def UsersURL():
"""URL of Users
e.g. https://scodoc.xxx.fr/ScoDoc/DEPT/Scolarite/Users
diff --git a/app/templates/assiduites/pages/bilan_dept.j2 b/app/templates/assiduites/pages/bilan_dept.j2
new file mode 100644
index 000000000..f6afdf610
--- /dev/null
+++ b/app/templates/assiduites/pages/bilan_dept.j2
@@ -0,0 +1,160 @@
+{% include "assiduites/widgets/tableau_base.j2" %}
+
+ Attention, cet étudiant a trop d'absences
+
+
+
+
+ Justificatifs en attente (ou modifiés)
+ {% include "assiduites/widgets/tableau_justi.j2" %}
+
+
+
+ Année scolaire 2022-2023 Changer année:
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/templates/assiduites/pages/bilan_etud.j2 b/app/templates/assiduites/pages/bilan_etud.j2
new file mode 100644
index 000000000..a282c3c5f
--- /dev/null
+++ b/app/templates/assiduites/pages/bilan_etud.j2
@@ -0,0 +1,342 @@
+{% block app_content %}
+{% include "assiduites/widgets/tableau_base.j2" %}
+
+
+
Bilan de l'assiduité de {{sco.etud.nomprenom}}
+
+
+ Attention, cet étudiant a trop d'absences
+
+
+
+
+
+
+ Assiduités non justifiées (Uniquement les retards et les absences)
+ {% include "assiduites/widgets/tableau_assi.j2" %}
+
+ Justificatifs en attente (ou modifiés)
+ {% include "assiduites/widgets/tableau_justi.j2" %}
+
+
+
+
+ Boutons de suppresions (toute suppression est définitive)
+ Suppression des assiduités
+ Suppression des justificatifs
+
+
+
+
+
+
+
+{% endblock app_content %}
+
+
+
\ No newline at end of file
diff --git a/app/templates/assiduites/widgets/tableau_assi.j2 b/app/templates/assiduites/widgets/tableau_assi.j2
index 52114f24d..6d1ea2d33 100644
--- a/app/templates/assiduites/widgets/tableau_assi.j2
+++ b/app/templates/assiduites/widgets/tableau_assi.j2
@@ -59,6 +59,8 @@
assi = filterArray(assi, filterAssiduites.filters)
renderTableAssiduites(currentPageAssiduites, assi);
renderPaginationButtons(assi);
+
+ try { stats() } catch (_) { }
}
const moduleimpls = {}
@@ -109,6 +111,7 @@
row.appendChild(td)
})
+
row.addEventListener("contextmenu", openContext);
tableBodyAssiduites.appendChild(row);
diff --git a/app/templates/assiduites/widgets/tableau_base.j2 b/app/templates/assiduites/widgets/tableau_base.j2
index 5121cb9ac..242d2c044 100644
--- a/app/templates/assiduites/widgets/tableau_base.j2
+++ b/app/templates/assiduites/widgets/tableau_base.j2
@@ -132,6 +132,11 @@
function renderPaginationButtons(array, assi = true) {
const totalPages = Math.ceil(array.length / itemsPerPage);
if (totalPages <= 1) {
+ if (assi) {
+ paginationContainerAssiduites.innerHTML = ""
+ } else {
+ paginationContainerJustificatifs.innerHTML = ""
+ }
return;
}
@@ -139,14 +144,15 @@
paginationContainerAssiduites.innerHTML = ""
paginationContainerAssiduites.querySelector('#paginationAssi')?.addEventListener('change', (e) => {
currentPageAssiduites = e.target.value;
- renderTableAssiduites(currentPageAssiduites, array);
+ assiduiteCallBack(array);
})
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
if (currentPageAssiduites > 1) {
currentPageAssiduites--;
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
- renderTableAssiduites(currentPageAssiduites, array);
+ assiduiteCallBack(array);
+
}
})
@@ -154,21 +160,21 @@
if (currentPageAssiduites < totalPages) {
currentPageAssiduites++;
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
- renderTableAssiduites(currentPageAssiduites, array);
+ assiduiteCallBack(array);
}
})
} else {
paginationContainerJustificatifs.innerHTML = ""
paginationContainerJustificatifs.querySelector('#paginationJusti')?.addEventListener('change', (e) => {
currentPageJustificatifs = e.target.value;
- renderTableJustificatifs(currentPageJustificatifs, array);
+ justificatifCallBack(array);
})
paginationContainerJustificatifs.querySelector('.pagination_moins').addEventListener('click', () => {
if (currentPageJustificatifs > 1) {
currentPageJustificatifs--;
paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageAssiduites
- renderTableJustificatifs(currentPageJustificatifs, array);
+ justificatifCallBack(array);
}
})
@@ -176,7 +182,7 @@
if (currentPageJustificatifs < totalPages) {
currentPageJustificatifs++;
paginationContainerJustificatifs.querySelector('#paginationJusti').value = currentPageAssiduites
- renderTableJustificatifs(currentPageJustificatifs, array);
+ justificatifCallBack(array);
}
})
}
@@ -624,6 +630,8 @@
return "Raison";
case "fichier":
return "Fichier";
+ case "etudid":
+ return "Etudiant";
}
}
@@ -776,7 +784,7 @@
margin-left: 2px !important;
}
- label {
+ .filter-body label {
display: flex;
justify-content: center;
align-items: center;
diff --git a/app/templates/assiduites/widgets/tableau_justi.j2 b/app/templates/assiduites/widgets/tableau_justi.j2
index c1808640e..a10041eb8 100644
--- a/app/templates/assiduites/widgets/tableau_justi.j2
+++ b/app/templates/assiduites/widgets/tableau_justi.j2
@@ -57,6 +57,17 @@
renderPaginationButtons(justi, false);
}
+
+ function getEtudiant(id) {
+ if (id in etuds) {
+ return etuds[id];
+ }
+ getSingleEtud(id);
+
+ return etuds[id];
+
+ }
+
function renderTableJustificatifs(page, justificatifs) {
generateTableHead(filterJustificatifs.columns, false)
@@ -85,9 +96,13 @@
td.textContent = moment.tz(justificatif[k], TIMEZONE).format(`DD/MM/Y HH:mm`)
} else if (k.indexOf('fichier') != -1) {
td.textContent = justificatif.fichier ? "Oui" : "Non";
+ } else if (k.indexOf('etudid') != -1) {
+ const e = getEtudiant(justificatif.etudid);
+
+ td.textContent = `${e.prenom.capitalize()} ${e.nom.toUpperCase()}`;
}
else {
- td.textContent = justificatif[k].capitalize()
+ td.textContent = `${justificatif[k]}`.capitalize()
}
row.appendChild(td)
diff --git a/app/templates/sidebar.j2 b/app/templates/sidebar.j2
index 6a607db79..8b8d559f4 100755
--- a/app/templates/sidebar.j2
+++ b/app/templates/sidebar.j2
@@ -24,7 +24,7 @@
-
+
{% if current_user.has_permission(sco.Permission.ScoUsersAdmin)
or current_user.has_permission(sco.Permission.ScoUsersView)
@@ -73,6 +73,8 @@
etudid=sco.etud.id) }}">Calendrier
Liste
+ Bilan
{% endif %}
{# /etud-insidebar #}
diff --git a/app/views/assiduites.py b/app/views/assiduites.py
index 464b4cc53..8c2fed0c5 100644
--- a/app/views/assiduites.py
+++ b/app/views/assiduites.py
@@ -10,7 +10,7 @@ from app.decorators import (
scodoc,
permission_required,
)
-from app.models import FormSemestre, Identite, ScoDocSiteConfig, Assiduite
+from app.models import FormSemestre, Identite, ScoDocSiteConfig, Assiduite, Departement
from app.views import assiduites_bp as bp
from app.views import ScoData
@@ -125,36 +125,49 @@ class HTMLBuilder:
@permission_required(Permission.ScoView)
def index_html():
"""Gestionnaire assiduités, page principale"""
-
H = [
html_sco_header.sco_header(
page_title="Saisie des assiduités",
- cssstyles=["css/calabs.css"],
- javascripts=["js/calabs.js"],
+ javascripts=[
+ "js/assiduites.js",
+ "libjs/moment.new.min.js",
+ "libjs/moment-timezone.js",
+ ],
+ cssstyles=[
+ "css/assiduites.css",
+ ],
),
"""Traitement des assiduités
Pour saisir des assiduités ou consulter les états, il est recommandé par passer par
- le semestre concerné (saisie par jours nommés ou par semaines).
+ le semestre concerné (saisie par jour ou saisie différée).
""",
]
H.append(
"""Pour signaler, annuler ou justifier une assiduité pour un seul étudiant,
- choisissez d'abord concerné:
"""
+ choisissez d'abord le concerné:
"""
)
H.append(sco_find_etud.form_search_etud())
- if current_user.has_permission(
- Permission.ScoAbsChange
- ) and sco_preferences.get_preference("handle_billets_abs"):
- H.append(
- f"""
- Billets d'absence
-
- """
- )
+ # if current_user.has_permission(
+ # Permission.ScoAbsChange
+ # ) and sco_preferences.get_preference("handle_billets_abs"):
+ # H.append(
+ # f"""
+ # Billets d'absence
+ #
+ # """
+ # )
+
+ H.append(
+ render_template(
+ "assiduites/pages/bilan_dept.j2",
+ dept_id=g.scodoc_dept_id,
+ annee=scu.annee_scolaire(),
+ ),
+ )
H.append(html_sco_header.sco_footer())
return "\n".join(H)
@@ -269,6 +282,60 @@ def liste_assiduites_etud():
).build()
+@bp.route("/BilanEtud")
+@scodoc
+@permission_required(Permission.ScoAbsChange)
+def bilan_etud():
+ """
+ bilan_etud Affichage de toutes les assiduites et justificatifs d'un etudiant
+ Args:
+ etudid (int): l'identifiant de l'étudiant
+
+ Returns:
+ str: l'html généré
+ """
+
+ etudid = request.args.get("etudid", -1)
+ etud: Identite = Identite.query.get_or_404(etudid)
+ if etud.dept_id != g.scodoc_dept_id:
+ abort(404, "étudiant inexistant dans ce département")
+
+ header: str = html_sco_header.sco_header(
+ page_title="Bilan de l'assiduité étudiante",
+ init_qtip=True,
+ javascripts=[
+ "js/assiduites.js",
+ "libjs/moment.new.min.js",
+ "libjs/moment-timezone.js",
+ ],
+ cssstyles=CSSSTYLES
+ + [
+ "css/assiduites.css",
+ ],
+ )
+
+ date_debut: str = f"{scu.annee_scolaire()}-09-01"
+ date_fin: str = f"{scu.annee_scolaire()+1}-06-30"
+
+ assi_metric = {
+ "H.": "heure",
+ "J.": "journee",
+ "1/2 J.": "demi",
+ }.get(sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id))
+
+ return HTMLBuilder(
+ header,
+ render_template(
+ "assiduites/pages/bilan_etud.j2",
+ sco=ScoData(etud),
+ date_debut=date_debut,
+ date_fin=date_fin,
+ assi_metric=assi_metric,
+ assi_seuil=_get_seuil(),
+ ),
+ ).build()
+
+
@bp.route("/AjoutJustificatifEtud")
@scodoc
@permission_required(Permission.ScoAbsChange)
@@ -549,7 +616,7 @@ def get_etat_abs_date():
etat = scu.EtatAssiduite.inverse().get(assi.etat).name
etudiant = {
- "nom": f'{etud["nomprenom"]} ',
+ "nom": f'{etud["nomprenom"]} ',
"etat": etat,
}
@@ -777,3 +844,7 @@ def _str_to_num(string: str):
def get_time(label: str, default: str):
return _str_to_num(ScoDocSiteConfig.get(label, default))
+
+
+def _get_seuil():
+ return sco_preferences.get_preference("assi_seuil", dept_id=g.scodoc_dept_id)
diff --git a/scodoc.py b/scodoc.py
index ee1a8edf8..21a66cbcb 100755
--- a/scodoc.py
+++ b/scodoc.py
@@ -655,22 +655,16 @@ def profile(host, port, length, profile_dir):
"-m",
"--morning",
help="Spécifie l'heure de début des cours format `hh:mm`",
- default="Heure configurée dans la configuration générale / 08:00 sinon",
- show_default=True,
)
@click.option(
"-n",
"--noon",
help="Spécifie l'heure de fin du matin (et donc début de l'après-midi) format `hh:mm`",
- default="Heure configurée dans la configuration générale / 13:00 sinon",
- show_default=True,
)
@click.option(
"-e",
"--evening",
help="Spécifie l'heure de fin des cours format `hh:mm`",
- default="Heure configurée dans la configuration générale / 18:00 sinon",
- show_default=True,
)
@with_appcontext
def migrate_abs_to_assiduites(
diff --git a/tools/migrate_abs_to_assiduites.py b/tools/migrate_abs_to_assiduites.py
index b64586098..3860e18aa 100644
--- a/tools/migrate_abs_to_assiduites.py
+++ b/tools/migrate_abs_to_assiduites.py
@@ -228,22 +228,22 @@ def migrate_abs_to_assiduites(
_glob.DEBUG = debug
if morning is None:
- _glob.MORNING = ScoDocSiteConfig.get("assi_morning_time", time(8, 0))
- else:
- morning: list[str] = morning.split(":")
- _glob.MORNING = time(int(morning[0]), int(morning[1]))
+ morning = ScoDocSiteConfig.get("assi_morning_time", time(8, 0))
+
+ morning: list[str] = morning.split(":")
+ _glob.MORNING = time(int(morning[0]), int(morning[1]))
if noon is None:
- _glob.NOON = ScoDocSiteConfig.get("assi_lunch_time", time(13, 0))
- else:
- noon: list[str] = noon.split(":")
- _glob.NOON = time(int(noon[0]), int(noon[1]))
+ noon = ScoDocSiteConfig.get("assi_lunch_time", time(13, 0))
+
+ noon: list[str] = noon.split(":")
+ _glob.NOON = time(int(noon[0]), int(noon[1]))
if evening is None:
- _glob.EVENING = ScoDocSiteConfig.get("assi_afternoon_time", time(18, 0))
- else:
- evening: list[str] = evening.split(":")
- _glob.EVENING = time(int(evening[0]), int(evening[1]))
+ evening = ScoDocSiteConfig.get("assi_afternoon_time", time(18, 0))
+
+ evening: list[str] = evening.split(":")
+ _glob.EVENING = time(int(evening[0]), int(evening[1]))
if dept is None:
prof_total = Profiler("MigrationTotal")