diff --git a/app/api/justificatifs.py b/app/api/justificatifs.py index e68b397ac31938de602f5ce1a0c76af7ed101eb3..f753fc0e0f5e9bb7fe04c136594d21ae400564e5 100644 --- a/app/api/justificatifs.py +++ b/app/api/justificatifs.py @@ -19,7 +19,13 @@ from app.api import api_bp as bp from app.api import api_web_bp from app.api import get_model_api_object, tools from app.decorators import permission_required, scodoc -from app.models import Identite, Justificatif, Departement, FormSemestre +from app.models import ( + Identite, + Justificatif, + Departement, + FormSemestre, + FormSemestreInscription, +) from app.models.assiduites import ( compute_assiduites_justified, ) @@ -27,6 +33,7 @@ from app.scodoc.sco_archives_justificatifs import JustificatifArchiver from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_permissions import Permission from app.scodoc.sco_utils import json_error +from app.scodoc.sco_groups import get_group_members # Partie Modèle @@ -145,14 +152,40 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False): 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) + for just in justificatifs_query: + data_set.append(_set_sems_and_groupe(just)) return data_set +def _set_sems_and_groupe(justi: Justificatif) -> dict: + from app.scodoc.sco_groups import get_etud_groups + + data = justi.to_dict(format_api=True) + + formsemestre: FormSemestre = ( + FormSemestre.query.join( + FormSemestreInscription, + FormSemestre.id == FormSemestreInscription.formsemestre_id, + ) + .filter( + justi.date_debut <= FormSemestre.date_fin, + justi.date_fin >= FormSemestre.date_debut, + FormSemestreInscription.etudid == justi.etudid, + ) + .first() + ) + if formsemestre: + data["formsemestre"] = { + "id": formsemestre.id, + "title": formsemestre.session_id(), + } + + return data + + @bp.route( "/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False} ) @@ -732,13 +765,16 @@ def _filter_manager(requested, justificatifs_query): # cas 5 : formsemestre_id formsemestre_id = requested.args.get("formsemestre_id") - if formsemestre_id is not None: + if formsemestre_id not in [None, "", -1]: formsemestre: FormSemestre = None - formsemestre_id = int(formsemestre_id) - formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() - justificatifs_query = scass.filter_by_formsemestre( - justificatifs_query, Justificatif, formsemestre - ) + try: + formsemestre_id = int(formsemestre_id) + formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first() + justificatifs_query = scass.filter_by_formsemestre( + justificatifs_query, Justificatif, formsemestre + ) + except ValueError: + formsemestre = None order = requested.args.get("order", None) if order is not None: @@ -755,4 +791,15 @@ def _filter_manager(requested, justificatifs_query): Justificatif.date_fin <= scu.date_fin_anne_scolaire(annee), ) + group_id = requested.args.get("group_id", None) + if group_id is not None: + try: + group_id = int(group_id) + etudids: list[int] = [etu["etudid"] for etu in get_group_members(group_id)] + justificatifs_query = justificatifs_query.filter( + Justificatif.etudid.in_(etudids) + ) + except ValueError: + group_id = None + return justificatifs_query diff --git a/app/scodoc/sco_formsemestre_status.py b/app/scodoc/sco_formsemestre_status.py index 7e67e4af8c999154c841ad40d6e4ecc8826aac00..c21e8774dc2d3088bccce12652383f623a297124 100755 --- a/app/scodoc/sco_formsemestre_status.py +++ b/app/scodoc/sco_formsemestre_status.py @@ -852,6 +852,11 @@ def _make_listes_sem(formsemestre: FormSemestre, with_absences=True): }?group_ids=%(group_id)s&formsemestre_id={ formsemestre.formsemestre_id }"><button>Saisie différée</button></a> + <a class="btn" href="{ + url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept) + }?group_id=%(group_id)s&formsemestre_id={ + formsemestre.formsemestre_id + }"><button>Justificatifs en attente</button></a> </td> """ else: diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index 728ccb363552da0d92a6486aad7facb245ebebf9..0eefe05805ddceded46bfdd1b2fbb345e67298b1 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -900,7 +900,7 @@ def form_choix_jour_saisie_hebdo(groups_infos, moduleimpl_id=None): formsemestre_id=groups_infos.formsemestre_id, moduleimpl_id="" if moduleimpl_id is None else moduleimpl_id ) - }';">Saisie du jour</button> + }';">Saisie du jour ({datetime.date.today().strftime('%d/%m/%Y')})</button> """ diff --git a/app/scodoc/sco_saisie_notes.py b/app/scodoc/sco_saisie_notes.py index 058c53efb6e056c6d9dfd8c7ef67f4b1a20d3ed3..489165bd01e3a0b6c3671727d4009bb17396c11d 100644 --- a/app/scodoc/sco_saisie_notes.py +++ b/app/scodoc/sco_saisie_notes.py @@ -46,6 +46,7 @@ from app.models import ( Module, ModuleImpl, ScolarNews, + Assiduite, ) from app.models.etudiants import Identite @@ -75,6 +76,8 @@ import app.scodoc.sco_utils as scu from app.scodoc.sco_utils import json_error from app.scodoc.sco_utils import ModuleType +from flask_sqlalchemy.query import Query + def convert_note_from_string( note: str, @@ -1102,30 +1105,21 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in # Groupes auxquels appartient cet étudiant: e["groups"] = sco_groups.get_etud_groups(etudid, formsemestre_id) - # Information sur absence (tenant compte de la demi-journée) - jour_iso = ( - evaluation.date_debut.date().isoformat() if evaluation.date_debut else "" - ) - warn_abs_lst = [] - # XXX TODO-ASSIDUITE (issue #686) - if evaluation.is_matin(): - nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True) - nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True) - if nbabs: - if nbabsjust: - warn_abs_lst.append("absent justifié le matin !") - else: - warn_abs_lst.append("absent le matin !") - if evaluation.is_apresmidi(): - nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=0) - nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=0) - if nbabs: - if nbabsjust: - warn_abs_lst.append("absent justifié l'après-midi !") - else: - warn_abs_lst.append("absent l'après-midi !") - - e["absinfo"] = '<span class="sn_abs">' + " ".join(warn_abs_lst) + "</span> " + # Information sur absence + warn_abs_lst: str = "" + if evaluation.date_debut is not None and evaluation.date_fin is not None: + assiduites_etud: Query = etud.assiduites.filter( + Assiduite.etat == scu.EtatAssiduite.ABSENT, + Assiduite.date_debut <= evaluation.date_fin, + Assiduite.date_fin >= evaluation.date_debut, + ) + premiere_assi: Assiduite = assiduites_etud.first() + if premiere_assi is not None: + warn_abs_lst: str = ( + f"absent {'justifié' if premiere_assi.est_just else ''}" + ) + + e["absinfo"] = '<span class="sn_abs">' + warn_abs_lst + "</span> " # Note actuelle de l'étudiant: if etudid in notes_db: diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index 7cbc3e0d200dbc55663b14a083fb8e2dab6c957a..f92ab38e38d877482212e24920bdbfc1d569125d 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -670,8 +670,8 @@ def AbsencesURL(): def AssiduitesURL(): """URL of Assiduités""" - return url_for("assiduites.index_html", scodoc_dept=g.scodoc_dept)[ - : -len("/index_html") + return url_for("assiduites.bilan_dept", scodoc_dept=g.scodoc_dept)[ + : -len("/BilanDept") ] diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 019cb894a2849296d0a371bcc48c107e06a0ed30..dbfa3736e15eb76c0acc0f60124d00872f6e28d5 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -577,7 +577,7 @@ function updateDate() { return true; } else { const att = document.createTextNode( - "Le jour sélectionné n'est pas un jour travaillé." + `Le jour sélectionné (${formatDate(date)}) n'est pas un jour travaillé.` ); openAlertModal("Erreur", att, "", "crimson"); dateInput.value = dateInput.getAttribute("value"); diff --git a/app/templates/assiduites/pages/bilan_dept.j2 b/app/templates/assiduites/pages/bilan_dept.j2 index 2e8ca5be96e5e91c40be5c098d6db51660a5eb0f..3301c85f6d8f14e8fdd160a417dc24dd92f9a07e 100644 --- a/app/templates/assiduites/pages/bilan_dept.j2 +++ b/app/templates/assiduites/pages/bilan_dept.j2 @@ -6,6 +6,7 @@ <section class="nonvalide"> <!-- Tableaux des justificatifs à valider (attente / modifié ) --> <h4>Justificatifs en attente (ou modifiés)</h4> + <a class="icon filter" onclick="filterJusti(true)"></a> {% include "assiduites/widgets/tableau_justi.j2" %} </section> @@ -34,14 +35,17 @@ generate(defAnnee) } + let formsemestre_id = "{{formsemestre_id}}" + let group_id = "{{group_id}}" + function getDeptJustificatifsFromPeriod(action) { - const path = getUrl() + `/api/justificatifs/dept/${dept_id}/query?date_debut=${bornes.deb}&date_fin=${bornes.fin}&etat=attente,modifie` + const formsemestre = formsemestre_id ? `&formsemestre_id=${formsemestre_id}` : "" + const group = group_id ? `&group_id=${group_id}` : "" + const path = getUrl() + `/api/justificatifs/dept/${dept_id}/query?date_debut=${bornes.deb}&date_fin=${bornes.fin}&etat=attente,modifie${formsemestre}${group}` async_get( path, (data, status) => { - console.log(data); justificatifCallBack(data); - }, (data, status) => { console.error(data, status) @@ -88,6 +92,7 @@ filterJustificatifs = { "columns": [ + "formsemestre", "etudid", "entry_date", "date_debut", @@ -100,7 +105,7 @@ "etat": [ "attente", "modifie" - ] + ], } } const select = document.querySelector('#annee'); diff --git a/app/templates/assiduites/pages/liste_assiduites.j2 b/app/templates/assiduites/pages/liste_assiduites.j2 index eb1f9ade3205da7406e5e6c3a105167f9aba21e9..aefc644bdf1129d3d9e106de2609b19e1b72482f 100644 --- a/app/templates/assiduites/pages/liste_assiduites.j2 +++ b/app/templates/assiduites/pages/liste_assiduites.j2 @@ -4,10 +4,10 @@ <h2>Liste de l'assiduité et des justificatifs de <span class="rouge">{{sco.etud.nomprenom}}</span></h2> {% include "assiduites/widgets/tableau_base.j2" %} <h3>Assiduités :</h3> - <a class="icon filter" onclick="filter()"></a> + <a class="icon filter" onclick="filterAssi()"></a> {% include "assiduites/widgets/tableau_assi.j2" %} <h3>Justificatifs :</h3> - <a class="icon filter" onclick="filter(false)"></a> + <a class="icon filter" onclick="filterJusti()"></a> {% include "assiduites/widgets/tableau_justi.j2" %} <ul id="contextMenu" class="context-menu"> <li id="detailOption">Detail</li> diff --git a/app/templates/assiduites/widgets/tableau_assi.j2 b/app/templates/assiduites/widgets/tableau_assi.j2 index fd1ee7df7eea9aedcf0fdf0e97017e82a3024e3a..b6f5349b498287c52c320afd82e492c563a71ad8 100644 --- a/app/templates/assiduites/widgets/tableau_assi.j2 +++ b/app/templates/assiduites/widgets/tableau_assi.j2 @@ -261,4 +261,185 @@ } ); } + + function filterAssi() { + let html = ` + <div class="filter-body"> + <h3>Affichage des colonnes:</h3> + <div class="filter-head"> + <label> + Date de saisie + <input class="chk" type="checkbox" name="entry_date" id="entry_date"> + </label> + <label> + Date de Début + <input class="chk" type="checkbox" name="date_debut" id="date_debut" checked> + </label> + <label> + Date de Fin + <input class="chk" type="checkbox" name="date_fin" id="date_fin" checked> + </label> + <label> + Etat + <input class="chk" type="checkbox" name="etat" id="etat" checked> + </label> + <label> + Module + <input class="chk" type="checkbox" name="moduleimpl_id" id="moduleimpl_id" checked> + </label> + <label> + Justifiée + <input class="chk" type="checkbox" name="est_just" id="est_just" checked> + </label> + </div> + <hr> + <h3>Filtrage des colonnes:</h3> + <span class="filter-line"> + <span class="filter-title" for="entry_date">Date de saisie</span> + <select name="entry_date_pref" id="entry_date_pref"> + <option value="-1">Avant</option> + <option value="0">Égal</option> + <option value="1">Après</option> + </select> + <input type="datetime-local" name="entry_date_time" id="entry_date_time"> + </span> + <span class="filter-line"> + <span class="filter-title" for="date_debut">Date de début</span> + <select name="date_debut_pref" id="date_debut_pref"> + <option value="-1">Avant</option> + <option value="0">Égal</option> + <option value="1">Après</option> + </select> + <input type="datetime-local" name="date_debut_time" id="date_debut_time"> + </span> + <span class="filter-line"> + <span class="filter-title" for="date_fin">Date de fin</span> + <select name="date_fin_pref" id="date_fin_pref"> + <option value="-1">Avant</option> + <option value="0">Égal</option> + <option value="1">Après</option> + </select> + <input type="datetime-local" name="date_fin_time" id="date_fin_time"> + </span> + <span class="filter-line"> + <span class="filter-title" for="etat">Etat</span> + <input checked type="checkbox" name="etat_present" id="etat_present" class="rbtn present" value="present"> + <input checked type="checkbox" name="etat_retard" id="etat_retard" class="rbtn retard" value="retard"> + <input checked type="checkbox" name="etat_absent" id="etat_absent" class="rbtn absent" value="absent"> + </span> + <span class="filter-line"> + <span class="filter-title" for="moduleimpl_id">Module</span> + <select id="moduleimpl_id"> + <option value="">Pas de filtre</option> + </select> + </span> + <span class="filter-line"> + <span class="filter-title" for="est_just">Est Justifiée</span> + <select id="est_just"> + <option value="">Pas de filtre</option> + <option value="true">Oui</option> + <option value="false">Non</option> + </select> + </span> + </div> + `; + const span = document.createElement('span'); + span.innerHTML = html + html = span.firstElementChild + + const filterHead = html.querySelector('.filter-head'); + filterHead.innerHTML = "" + let cols = ["entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"]; + + cols.forEach((k) => { + const label = document.createElement('label') + label.classList.add('f-label') + const s = document.createElement('span'); + s.textContent = columnTranslator(k); + + + const input = document.createElement('input'); + input.classList.add('chk') + input.type = "checkbox" + input.name = k + input.id = k; + input.checked = filterAssiduites.columns.includes(k) + + label.appendChild(s) + label.appendChild(input) + filterHead.appendChild(label) + }) + + const sl = html.querySelector('.filter-line #moduleimpl_id'); + let opts = [] + Object.keys(moduleimpls).forEach((k) => { + const opt = document.createElement('option'); + opt.value = k == null ? "null" : k; + opt.textContent = moduleimpls[k]; + opts.push(opt); + }) + + opts = opts.sort((a, b) => { + return a.value < b.value + }) + + sl.append(...opts); + + // Mise à jour des filtres + + Object.keys(filterAssiduites.filters).forEach((key) => { + const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement; + if (key.indexOf('date') != -1) { + l.querySelector(`#${key}_pref`).value = filterAssiduites.filters[key].pref; + l.querySelector(`#${key}_time`).value = filterAssiduites.filters[key].time.format("YYYY-MM-DDTHH:mm"); + + } else if (key.indexOf('etat') != -1) { + l.querySelectorAll('input').forEach((e) => { + e.checked = filterAssiduites.filters[key].includes(e.value) + }) + } else if (key.indexOf("module") != -1) { + l.querySelector('#moduleimpl_id').value = filterAssiduites.filters[key]; + } else if (key.indexOf("est_just") != -1) { + l.querySelector('#est_just').value = filterAssiduites.filters[key]; + } + }) + + openPromptModal("Filtrage des assiduités", html, () => { + + const columns = [...document.querySelectorAll('.chk')] + .map((el) => { if (el.checked) return el.id }) + .filter((el) => el) + + filterAssiduites.columns = columns + filterAssiduites.filters = {} + //reste des filtres + + const lines = [...document.querySelectorAll('.filter-line')]; + + lines.forEach((l) => { + const key = l.querySelector('.filter-title').getAttribute('for'); + + if (key.indexOf('date') != -1) { + const pref = l.querySelector(`#${key}_pref`).value; + const time = l.querySelector(`#${key}_time`).value; + if (l.querySelector(`#${key}_time`).value != "") { + filterAssiduites.filters[key] = { + pref: pref, + time: new moment.tz(time, TIMEZONE) + } + } + } else if (key.indexOf('etat') != -1) { + filterAssiduites.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value); + } else if (key.indexOf("module") != -1) { + filterAssiduites.filters[key] = l.querySelector('#moduleimpl_id').value; + } else if (key.indexOf("est_just") != -1) { + filterAssiduites.filters[key] = l.querySelector('#est_just').value; + } + }) + + + getAllAssiduitesFromEtud(etudid, assiduiteCallBack) + + }, () => { }, "#7059FF"); + } </script> \ No newline at end of file diff --git a/app/templates/assiduites/widgets/tableau_base.j2 b/app/templates/assiduites/widgets/tableau_base.j2 index 2b468e6a4d9dc19264fe312bee542b38ec01ac82..e337ebedadc2bbabe3bc103aa2d2400d90398cf9 100644 --- a/app/templates/assiduites/widgets/tableau_base.j2 +++ b/app/templates/assiduites/widgets/tableau_base.j2 @@ -102,6 +102,10 @@ return f.obj_id.includes(obj_id) } + if (k == "formsemestre") { + return f.formsemestre === "" || (el.hasOwnProperty("formsemestre") && el.formsemestre.title.replaceAll('-', ' ').indexOf(f.formsemestre) != -1); + } + return true; }) @@ -286,343 +290,6 @@ } - function filter(assi = true) { - if (assi) { - let html = ` - <div class="filter-body"> - <h3>Affichage des colonnes:</h3> - <div class="filter-head"> - <label> - Date de saisie - <input class="chk" type="checkbox" name="entry_date" id="entry_date"> - </label> - <label> - Date de Début - <input class="chk" type="checkbox" name="date_debut" id="date_debut" checked> - </label> - <label> - Date de Fin - <input class="chk" type="checkbox" name="date_fin" id="date_fin" checked> - </label> - <label> - Etat - <input class="chk" type="checkbox" name="etat" id="etat" checked> - </label> - <label> - Module - <input class="chk" type="checkbox" name="moduleimpl_id" id="moduleimpl_id" checked> - </label> - <label> - Justifiée - <input class="chk" type="checkbox" name="est_just" id="est_just" checked> - </label> - </div> - <hr> - <h3>Filtrage des colonnes:</h3> - <span class="filter-line"> - <span class="filter-title" for="entry_date">Date de saisie</span> - <select name="entry_date_pref" id="entry_date_pref"> - <option value="-1">Avant</option> - <option value="0">Égal</option> - <option value="1">Après</option> - </select> - <input type="datetime-local" name="entry_date_time" id="entry_date_time"> - </span> - <span class="filter-line"> - <span class="filter-title" for="date_debut">Date de début</span> - <select name="date_debut_pref" id="date_debut_pref"> - <option value="-1">Avant</option> - <option value="0">Égal</option> - <option value="1">Après</option> - </select> - <input type="datetime-local" name="date_debut_time" id="date_debut_time"> - </span> - <span class="filter-line"> - <span class="filter-title" for="date_fin">Date de fin</span> - <select name="date_fin_pref" id="date_fin_pref"> - <option value="-1">Avant</option> - <option value="0">Égal</option> - <option value="1">Après</option> - </select> - <input type="datetime-local" name="date_fin_time" id="date_fin_time"> - </span> - <span class="filter-line"> - <span class="filter-title" for="etat">Etat</span> - <input checked type="checkbox" name="etat_present" id="etat_present" class="rbtn present" value="present"> - <input checked type="checkbox" name="etat_retard" id="etat_retard" class="rbtn retard" value="retard"> - <input checked type="checkbox" name="etat_absent" id="etat_absent" class="rbtn absent" value="absent"> - </span> - <span class="filter-line"> - <span class="filter-title" for="moduleimpl_id">Module</span> - <select id="moduleimpl_id"> - <option value="">Pas de filtre</option> - </select> - </span> - <span class="filter-line"> - <span class="filter-title" for="est_just">Est Justifiée</span> - <select id="est_just"> - <option value="">Pas de filtre</option> - <option value="true">Oui</option> - <option value="false">Non</option> - </select> - </span> - </div> - `; - const span = document.createElement('span'); - span.innerHTML = html - html = span.firstElementChild - - const filterHead = html.querySelector('.filter-head'); - filterHead.innerHTML = "" - let cols = ["entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"]; - - cols.forEach((k) => { - const label = document.createElement('label') - label.classList.add('f-label') - const s = document.createElement('span'); - s.textContent = columnTranslator(k); - - - const input = document.createElement('input'); - input.classList.add('chk') - input.type = "checkbox" - input.name = k - input.id = k; - input.checked = filterAssiduites.columns.includes(k) - - label.appendChild(s) - label.appendChild(input) - filterHead.appendChild(label) - }) - - const sl = html.querySelector('.filter-line #moduleimpl_id'); - let opts = [] - Object.keys(moduleimpls).forEach((k) => { - const opt = document.createElement('option'); - opt.value = k == null ? "null" : k; - opt.textContent = moduleimpls[k]; - opts.push(opt); - }) - - opts = opts.sort((a, b) => { - return a.value < b.value - }) - - sl.append(...opts); - - // Mise à jour des filtres - - Object.keys(filterAssiduites.filters).forEach((key) => { - const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement; - if (key.indexOf('date') != -1) { - l.querySelector(`#${key}_pref`).value = filterAssiduites.filters[key].pref; - l.querySelector(`#${key}_time`).value = filterAssiduites.filters[key].time.format("YYYY-MM-DDTHH:mm"); - - } else if (key.indexOf('etat') != -1) { - l.querySelectorAll('input').forEach((e) => { - e.checked = filterAssiduites.filters[key].includes(e.value) - }) - } else if (key.indexOf("module") != -1) { - l.querySelector('#moduleimpl_id').value = filterAssiduites.filters[key]; - } else if (key.indexOf("est_just") != -1) { - l.querySelector('#est_just').value = filterAssiduites.filters[key]; - } - }) - - openPromptModal("Filtrage des assiduités", html, () => { - - const columns = [...document.querySelectorAll('.chk')] - .map((el) => { if (el.checked) return el.id }) - .filter((el) => el) - - filterAssiduites.columns = columns - filterAssiduites.filters = {} - //reste des filtres - - const lines = [...document.querySelectorAll('.filter-line')]; - - lines.forEach((l) => { - const key = l.querySelector('.filter-title').getAttribute('for'); - - if (key.indexOf('date') != -1) { - const pref = l.querySelector(`#${key}_pref`).value; - const time = l.querySelector(`#${key}_time`).value; - if (l.querySelector(`#${key}_time`).value != "") { - filterAssiduites.filters[key] = { - pref: pref, - time: new moment.tz(time, TIMEZONE) - } - } - } else if (key.indexOf('etat') != -1) { - filterAssiduites.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value); - } else if (key.indexOf("module") != -1) { - filterAssiduites.filters[key] = l.querySelector('#moduleimpl_id').value; - } else if (key.indexOf("est_just") != -1) { - filterAssiduites.filters[key] = l.querySelector('#est_just').value; - } - }) - - - getAllAssiduitesFromEtud(etudid, assiduiteCallBack) - - }, () => { }, "#7059FF"); - } else { - let html = ` - <div class="filter-body"> - <h3>Affichage des colonnes:</h3> - <div class="filter-head"> - <label> - Date de saisie - <input class="chk" type="checkbox" name="entry_date" id="entry_date"> - </label> - <label> - Date de Début - <input class="chk" type="checkbox" name="date_debut" id="date_debut" checked> - </label> - <label> - Date de Fin - <input class="chk" type="checkbox" name="date_fin" id="date_fin" checked> - </label> - <label> - Etat - <input class="chk" type="checkbox" name="etat" id="etat" checked> - </label> - <label> - Raison - <input class="chk" type="checkbox" name="raison" id="raison" checked> - </label> - <label> - Fichier - <input class="chk" type="checkbox" name="fichier" id="fichier" checked> - </label> - </div> - <hr> - <h3>Filtrage des colonnes:</h3> - <span class="filter-line"> - <span class="filter-title" for="entry_date">Date de saisie</span> - <select name="entry_date_pref" id="entry_date_pref"> - <option value="-1">Avant</option> - <option value="0">Égal</option> - <option value="1">Après</option> - </select> - <input type="datetime-local" name="entry_date_time" id="entry_date_time"> - </span> - <span class="filter-line"> - <span class="filter-title" for="date_debut">Date de début</span> - <select name="date_debut_pref" id="date_debut_pref"> - <option value="-1">Avant</option> - <option value="0">Égal</option> - <option value="1">Après</option> - </select> - <input type="datetime-local" name="date_debut_time" id="date_debut_time"> - </span> - <span class="filter-line"> - <span class="filter-title" for="date_fin">Date de fin</span> - <select name="date_fin_pref" id="date_fin_pref"> - <option value="-1">Avant</option> - <option value="0">Égal</option> - <option value="1">Après</option> - </select> - <input type="datetime-local" name="date_fin_time" id="date_fin_time"> - </span> - <span class="filter-line"> - <span class="filter-title" for="etat">Etat</span> - <label> - Valide - <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="valide"> - </label> - <label> - Non Valide - <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="non_valide"> - </label> - <label> - En Attente - <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="attente"> - </label> - <label> - Modifié - <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="modifie"> - </label> - </span> - </div> - `; - const span = document.createElement('span'); - span.innerHTML = html - html = span.firstElementChild - - const filterHead = html.querySelector('.filter-head'); - filterHead.innerHTML = "" - let cols = ["entry_date", "date_debut", "date_fin", "etat", "raison", "fichier"]; - - cols.forEach((k) => { - const label = document.createElement('label') - label.classList.add('f-label') - const s = document.createElement('span'); - s.textContent = columnTranslator(k); - - - const input = document.createElement('input'); - input.classList.add('chk') - input.type = "checkbox" - input.name = k - input.id = k; - input.checked = filterJustificatifs.columns.includes(k) - - label.appendChild(s) - label.appendChild(input) - filterHead.appendChild(label) - }) - - // Mise à jour des filtres - - Object.keys(filterJustificatifs.filters).forEach((key) => { - const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement; - if (key.indexOf('date') != -1) { - l.querySelector(`#${key}_pref`).value = filterJustificatifs.filters[key].pref; - l.querySelector(`#${key}_time`).value = filterJustificatifs.filters[key].time.format("YYYY-MM-DDTHH:mm"); - - } else if (key.indexOf('etat') != -1) { - l.querySelectorAll('input').forEach((e) => { - e.checked = filterJustificatifs.filters[key].includes(e.value) - }) - } - }) - - openPromptModal("Filtrage des Justificatifs", html, () => { - - const columns = [...document.querySelectorAll('.chk')] - .map((el) => { if (el.checked) return el.id }) - .filter((el) => el) - - filterJustificatifs.columns = columns - filterJustificatifs.filters = {} - //reste des filtres - - const lines = [...document.querySelectorAll('.filter-line')]; - - lines.forEach((l) => { - const key = l.querySelector('.filter-title').getAttribute('for'); - - if (key.indexOf('date') != -1) { - const pref = l.querySelector(`#${key}_pref`).value; - const time = l.querySelector(`#${key}_time`).value; - if (l.querySelector(`#${key}_time`).value != "") { - filterJustificatifs.filters[key] = { - pref: pref, - time: new moment.tz(time, TIMEZONE) - } - } - } else if (key.indexOf('etat') != -1) { - filterJustificatifs.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value); - } - }) - - - getAllJustificatifsFromEtud(etudid, justificatifCallBack) - - }, () => { }, "#7059FF"); - } - } function columnTranslator(colName) { switch (colName) { @@ -644,6 +311,8 @@ return "Fichier"; case "etudid": return "Etudiant"; + case "formsemestre": + return "Semestre"; } } diff --git a/app/templates/assiduites/widgets/tableau_justi.j2 b/app/templates/assiduites/widgets/tableau_justi.j2 index 4b3a50228986914c3e268598ddb0e99ae8805783..636f9ede514f2e9600909f9c23fc78bb998bf947 100644 --- a/app/templates/assiduites/widgets/tableau_justi.j2 +++ b/app/templates/assiduites/widgets/tableau_justi.j2 @@ -99,7 +99,13 @@ } else if (k.indexOf('etudid') != -1) { const e = getEtudiant(justificatif.etudid); - td.textContent = `${e.prenom.capitalize()} ${e.nom.toUpperCase()}`; + td.innerHTML = `<a class="etudinfo" id="line-${justificatif.etudid}" href="BilanEtud?etudid=${justificatif.etudid}">${e.prenom.capitalize()} ${e.nom.toUpperCase()}</a>`; + } else if (k == "formsemestre") { + if (justificatif.hasOwnProperty("formsemestre")) { + td.textContent = justificatif.formsemestre.title.replaceAll('-', ' '); + } else { + td.textContent = `Pas de Semestre`; + } } else { if (justificatif[k] != null) { @@ -458,7 +464,191 @@ }) } + function filterJusti(dept = false) { + + let dept_html_head = ` + <label> + Semestre + <input class="chk" type="checkbox" name="formsemestre" id="formsemestre" checked> + </label> + <label> + Etudiant + <input class="chk" type="checkbox" name="etudid" id="etudid" checked> + </label> + ` + + let dept_html_body = ` + <span class="filter-line"> + <span class="filter-title" for="formsemestre">Recherche dans les semestre</span> + <input type="text" name="formsemestre" id="formsemestre" placeholder="S1 2023" > + </span> + ` + + let html = ` + <div class="filter-body"> + <h3>Affichage des colonnes:</h3> + <div class="filter-head"> + ${dept ? dept_html_head : ""} + <label> + Date de saisie + <input class="chk" type="checkbox" name="entry_date" id="entry_date"> + </label> + <label> + Date de Début + <input class="chk" type="checkbox" name="date_debut" id="date_debut" checked> + </label> + <label> + Date de Fin + <input class="chk" type="checkbox" name="date_fin" id="date_fin" checked> + </label> + <label> + Etat + <input class="chk" type="checkbox" name="etat" id="etat" checked> + </label> + <label> + Raison + <input class="chk" type="checkbox" name="raison" id="raison" checked> + </label> + <label> + Fichier + <input class="chk" type="checkbox" name="fichier" id="fichier" checked> + </label> + </div> + <hr> + <h3>Filtrage des colonnes:</h3> + <span class="filter-line"> + <span class="filter-title" for="entry_date">Date de saisie</span> + <select name="entry_date_pref" id="entry_date_pref"> + <option value="-1">Avant</option> + <option value="0">Égal</option> + <option value="1">Après</option> + </select> + <input type="datetime-local" name="entry_date_time" id="entry_date_time"> + </span> + <span class="filter-line"> + <span class="filter-title" for="date_debut">Date de début</span> + <select name="date_debut_pref" id="date_debut_pref"> + <option value="-1">Avant</option> + <option value="0">Égal</option> + <option value="1">Après</option> + </select> + <input type="datetime-local" name="date_debut_time" id="date_debut_time"> + </span> + <span class="filter-line"> + <span class="filter-title" for="date_fin">Date de fin</span> + <select name="date_fin_pref" id="date_fin_pref"> + <option value="-1">Avant</option> + <option value="0">Égal</option> + <option value="1">Après</option> + </select> + <input type="datetime-local" name="date_fin_time" id="date_fin_time"> + </span> + <span class="filter-line"> + <span class="filter-title" for="etat">Etat</span> + <label> + Valide + <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="valide"> + </label> + <label> + Non Valide + <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="non_valide"> + </label> + <label> + En Attente + <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="attente"> + </label> + <label> + Modifié + <input checked type="checkbox" name="etat_valide" id="etat_valide" class="" value="modifie"> + </label> + </span> + ${dept ? dept_html_body : ""} + </div> + `; + const span = document.createElement('span'); + span.innerHTML = html + html = span.firstElementChild + + const filterHead = html.querySelector('.filter-head'); + filterHead.innerHTML = "" + let cols = ["formsemestre", "etudid", "entry_date", "date_debut", "date_fin", "etat", "raison", "fichier"]; + + cols.forEach((k) => { + const label = document.createElement('label') + label.classList.add('f-label') + const s = document.createElement('span'); + s.textContent = columnTranslator(k); + + + const input = document.createElement('input'); + input.classList.add('chk') + input.type = "checkbox" + input.name = k + input.id = k; + input.checked = filterJustificatifs.columns.includes(k) + + label.appendChild(s) + label.appendChild(input) + filterHead.appendChild(label) + }) + + // Mise à jour des filtres + + Object.keys(filterJustificatifs.filters).forEach((key) => { + const l = html.querySelector(`.filter-title[for="${key}"]`).parentElement; + if (key.indexOf('date') != -1) { + l.querySelector(`#${key}_pref`).value = filterJustificatifs.filters[key].pref; + l.querySelector(`#${key}_time`).value = filterJustificatifs.filters[key].time.format("YYYY-MM-DDTHH:mm"); + + } else if (key.indexOf('etat') != -1) { + l.querySelectorAll('input').forEach((e) => { + e.checked = filterJustificatifs.filters[key].includes(e.value) + }) + } else if (key == "formsemestre") { + l.querySelector('#formsemestre').value = filterJustificatifs.filters["formsemestre"]; + } + }) + + openPromptModal("Filtrage des Justificatifs", html, () => { + const columns = [...document.querySelectorAll('.chk')] + .map((el) => { if (el.checked) return el.id }) + .filter((el) => el) + + filterJustificatifs.columns = columns + filterJustificatifs.filters = {} + //reste des filtres + + const lines = [...document.querySelectorAll('.filter-line')]; + + lines.forEach((l) => { + const key = l.querySelector('.filter-title').getAttribute('for'); + + if (key.indexOf('date') != -1) { + const pref = l.querySelector(`#${key}_pref`).value; + const time = l.querySelector(`#${key}_time`).value; + if (l.querySelector(`#${key}_time`).value != "") { + filterJustificatifs.filters[key] = { + pref: pref, + time: new moment.tz(time, TIMEZONE) + } + } + } else if (key.indexOf('etat') != -1) { + filterJustificatifs.filters[key] = [...l.querySelectorAll("input:checked")].map((e) => e.value); + } else if (key == "formsemestre") { + filterJustificatifs.filters["formsemestre"] = l.querySelector('#formsemestre').value; + } + }) + + + if (dept) { + loadAll(); + } else { + getAllJustificatifsFromEtud(etudid, justificatifCallBack) + } + + }, () => { }, "#7059FF"); + } </script> <style> .fich-file { diff --git a/app/templates/sidebar.j2 b/app/templates/sidebar.j2 index 762b73be18927746e5e4d5454a1f0ed39e260cad..560cacfc2d72d49a18153f4056fff8717bfe9df2 100755 --- a/app/templates/sidebar.j2 +++ b/app/templates/sidebar.j2 @@ -26,7 +26,7 @@ <a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br> {% if current_user.has_permission(sco.Permission.ScoAbsChange)%} - <a href="{{url_for('assiduites.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Assiduités</a> <br> + <a href="{{url_for('assiduites.bilan_dept', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Assiduités</a> <br> {% endif %} {% if current_user.has_permission(sco.Permission.ScoUsersAdmin) or current_user.has_permission(sco.Permission.ScoUsersView) @@ -86,8 +86,7 @@ <div class="sidebar-bottom"><a href="{{ url_for( 'scodoc.about', scodoc_dept=g.scodoc_dept ) }}" class="sidebar">À propos</a> <br /> - <a href="{{ scu.SCO_USER_MANUAL }}" - target="_blank" rel="noopener" class="sidebar">Aide</a> + <a href="{{ scu.SCO_USER_MANUAL }}" target="_blank" rel="noopener" class="sidebar">Aide</a> </div> </div> <div class="logo-logo"> diff --git a/app/views/assiduites.py b/app/views/assiduites.py index a2e5a3b1d7e02cb6d0b4bac90133c711171e7a3a..145e28a18e588fe1bd2c2364c3bb75ee7eedb73e 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -134,10 +134,10 @@ class HTMLBuilder: @bp.route("/") -@bp.route("/index_html") +@bp.route("/BilanDept") @scodoc @permission_required(Permission.ScoAbsChange) -def index_html(): +def bilan_dept(): """Gestionnaire assiduités, page principale""" H = [ html_sco_header.sco_header( @@ -180,17 +180,29 @@ def index_html(): reverse=True, ) + annee = scu.annee_scolaire() + annees_str: str = "[" for ann in annees: annees_str += f"{ann}," annees_str += "]" + formsemestre_id = request.args.get("formsemestre_id", "") + if formsemestre_id: + try: + formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) + annee = formsemestre.annee_scolaire() + except AttributeError: + formsemestre_id = "" + H.append( render_template( "assiduites/pages/bilan_dept.j2", dept_id=g.scodoc_dept_id, - annee=scu.annee_scolaire(), + annee=annee, annees=annees_str, + formsemestre_id=formsemestre_id, + group_id=request.args.get("group_id", ""), ), ) H.append(html_sco_header.sco_footer()) @@ -565,10 +577,14 @@ def signal_assiduites_group(): real_date = scu.is_iso_formated(date, True).date() - if real_date < formsemestre.date_debut: - date = formsemestre.date_debut.isoformat() - elif real_date > formsemestre.date_fin: - date = formsemestre.date_fin.isoformat() + if real_date < formsemestre.date_debut or real_date > formsemestre.date_fin: + real_str = real_date.strftime("%d/%m/%Y") + form_deb = formsemestre.date_debut.strftime("%d/%m/%Y") + form_fin = formsemestre.date_fin.strftime("%d/%m/%Y") + raise ScoValueError( + f"Impossible de saisir les assiduités pour le {real_str}" + + f" : Jour en dehors du semestre ( {form_deb} → {form_fin}) " + ) # --- Restriction en fonction du moduleimpl_id --- if moduleimpl_id: