forked from ScoDoc/ScoDoc
Assiduites : Pages Bilans
This commit is contained in:
parent
9b8e09cdba
commit
7a42c24fc4
@ -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/<int:dept_id>", defaults={"with_query": False})
|
||||
@api_web_bp.route(
|
||||
"/justificatifs/dept/<int:dept_id>/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/<int:etudid>/create", methods=["POST"])
|
||||
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
|
||||
@scodoc
|
||||
|
@ -54,7 +54,7 @@ def sidebar_common():
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{scu.ScoURL()}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{scu.NotesURL()}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{scu.AbsencesURL()}" class="sidebar">Absences</a> <br>
|
||||
<a href="{scu.AssiduitesURL()}" class="sidebar">Assiduités</a> <br>
|
||||
"""
|
||||
]
|
||||
if current_user.has_permission(
|
||||
@ -138,6 +138,7 @@ def sidebar(etudid: int = None):
|
||||
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.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>
|
||||
"""
|
||||
)
|
||||
|
@ -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",
|
||||
|
@ -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"],
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -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
|
||||
|
160
app/templates/assiduites/pages/bilan_dept.j2
Normal file
160
app/templates/assiduites/pages/bilan_dept.j2
Normal file
@ -0,0 +1,160 @@
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<section class="alerte invisible">
|
||||
<p>Attention, cet étudiant a trop d'absences</p>
|
||||
</section>
|
||||
|
||||
<section class="nonvalide">
|
||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
</section>
|
||||
|
||||
<div class="annee">
|
||||
<span>Année scolaire 2022-2023 Changer année: </span>
|
||||
<select name="" id="annee" onchange="setterAnnee(this.value)">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="legende">
|
||||
|
||||
</div>
|
||||
<script>
|
||||
|
||||
function loadAll() {
|
||||
generate(defAnnee)
|
||||
}
|
||||
|
||||
function getDeptJustificatifsFromPeriod(action) {
|
||||
const path = getUrl() + `/api/justificatifs/dept/${dept_id}/query?date_debut=${bornes.deb}&date_fin=${bornes.fin}&etat=attente,modifie`
|
||||
async_get(
|
||||
path,
|
||||
(data, status) => {
|
||||
console.log(data);
|
||||
justificatifCallBack(data);
|
||||
|
||||
},
|
||||
(data, status) => {
|
||||
console.error(data, status)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function generate(annee) {
|
||||
|
||||
if (annee < 1999 || annee > 2999) {
|
||||
openAlertModal("Année impossible", document.createTextNode("L'année demandé n'existe pas."));
|
||||
return;
|
||||
}
|
||||
bornes = {
|
||||
deb: `${annee}-09-01T00:00`,
|
||||
fin: `${annee + 1}-06-30T23:59`
|
||||
}
|
||||
|
||||
defAnnee = annee;
|
||||
|
||||
getDeptJustificatifsFromPeriod()
|
||||
|
||||
}
|
||||
|
||||
function setterAnnee(annee) {
|
||||
annee = parseInt(annee);
|
||||
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
|
||||
generate(annee)
|
||||
|
||||
}
|
||||
let defAnnee = {{ annee }};
|
||||
let bornes = {
|
||||
deb: `${defAnnee}-09-01T00:00`,
|
||||
fin: `${defAnnee + 1}-06-30T23:59`
|
||||
}
|
||||
const dept_id = {{ dept_id }};
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
|
||||
filterJustificatifs = {
|
||||
"columns": [
|
||||
"etudid",
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"etat",
|
||||
"raison",
|
||||
"fichier"
|
||||
],
|
||||
"filters": {
|
||||
"etat": [
|
||||
"attente",
|
||||
"modifie"
|
||||
]
|
||||
}
|
||||
}
|
||||
const select = document.querySelector('#annee');
|
||||
for (let i = defAnnee + 1; i > defAnnee - 6; i--) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = i + "",
|
||||
opt.textContent = i + "";
|
||||
if (i === defAnnee) {
|
||||
opt.selected = true;
|
||||
}
|
||||
select.appendChild(opt)
|
||||
}
|
||||
setterAnnee(defAnnee)
|
||||
})
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.stats-values-item {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stats {
|
||||
border: 1px solid #333;
|
||||
padding: 5px 2px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.stats-values {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stats-values-item h5 {
|
||||
font-weight: bold;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.stats-values-part {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerte {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border-radius: 7px;
|
||||
|
||||
background-color: crimson;
|
||||
|
||||
}
|
||||
|
||||
.alerte.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alerte p {
|
||||
font-size: larger;
|
||||
color: whitesmoke;
|
||||
|
||||
}
|
||||
|
||||
.suppr {
|
||||
margin: 5px 0;
|
||||
}
|
||||
</style>
|
342
app/templates/assiduites/pages/bilan_etud.j2
Normal file
342
app/templates/assiduites/pages/bilan_etud.j2
Normal file
@ -0,0 +1,342 @@
|
||||
{% block app_content %}
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<div class="pageContent">
|
||||
|
||||
<h2>Bilan de l'assiduité de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
||||
|
||||
<section class="alerte invisible">
|
||||
<p>Attention, cet étudiant a trop d'absences</p>
|
||||
</section>
|
||||
|
||||
<section class="stats">
|
||||
<!-- Statistiques d'assiduité (nb pres, nb retard, nb absence) + nb justifié -->
|
||||
<h4>Statistiques d'assiduité</h4>
|
||||
<div class="stats-inputs">
|
||||
<label class="stats-label"> Date de début<input type="date" name="stats_date_debut" id="stats_date_debut"
|
||||
value="{{date_debut}}"></label>
|
||||
<label class="stats-label"> Date de fin<input type="date" name="stats_date_fin" id="stats_date_fin"
|
||||
value="{{date_fin}}"></label>
|
||||
<button onclick="stats()">Actualiser</button>
|
||||
</div>
|
||||
|
||||
<div class="stats-values">
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="nonvalide">
|
||||
<!-- Tableaux des assiduités (retard/abs) non justifiées -->
|
||||
<h4>Assiduités non justifiées (Uniquement les retards et les absences)</h4>
|
||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
|
||||
</section>
|
||||
|
||||
<section class="suppr">
|
||||
<h4>Boutons de suppresions (toute suppression est définitive) </h4>
|
||||
<button type="button" onclick="removeAllAssiduites()">Suppression des assiduités</button>
|
||||
<button type="button" onclick="removeAllJustificatifs()">Suppression des justificatifs</button>
|
||||
</section>
|
||||
|
||||
<div class="legende">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock app_content %}
|
||||
|
||||
<script>
|
||||
|
||||
function stats() {
|
||||
const dd_val = document.getElementById('stats_date_debut').value;
|
||||
const df_val = document.getElementById('stats_date_fin').value;
|
||||
|
||||
if (dd_val == "" || df_val == "") {
|
||||
openAlertModal("Dates invalides", document.createTextNode('Les dates sélectionnées sont invalides'));
|
||||
return;
|
||||
}
|
||||
|
||||
const date_debut = new moment.tz(dd_val + "T00:00", TIMEZONE);
|
||||
const date_fin = new moment.tz(df_val + "T23:59", TIMEZONE);
|
||||
|
||||
if (date_debut.valueOf() > date_fin.valueOf()) {
|
||||
openAlertModal("Dates invalides", document.createTextNode('La date de début se situe après la date de fin.'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
countAssiduites(date_debut.format(), date_fin.format())
|
||||
|
||||
}
|
||||
|
||||
function getAssiduitesCount(dateDeb, dateFin, query) {
|
||||
const url_api = getUrl() + `/api/assiduites/${etudid}/count/query?date_debut=${dateDeb}&date_fin=${dateFin}&${query}`;
|
||||
return $.ajax({
|
||||
async: true,
|
||||
type: "GET",
|
||||
url: url_api,
|
||||
success: (data, status) => {
|
||||
if (status === "success") {
|
||||
}
|
||||
},
|
||||
error: () => { },
|
||||
});
|
||||
}
|
||||
|
||||
function countAssiduites(dateDeb, dateFin) {
|
||||
$.when(
|
||||
getAssiduitesCount(dateDeb, dateFin, `etat=present`),
|
||||
getAssiduitesCount(dateDeb, dateFin, `etat=present&est_just=v`),
|
||||
getAssiduitesCount(dateDeb, dateFin, `etat=retard`),
|
||||
getAssiduitesCount(dateDeb, dateFin, `etat=retard&est_just=v`),
|
||||
getAssiduitesCount(dateDeb, dateFin, `etat=absent`),
|
||||
getAssiduitesCount(dateDeb, dateFin, `etat=absent&est_just=v`),
|
||||
).then(
|
||||
(pt, pj, rt, rj, at, aj) => {
|
||||
const counter = {
|
||||
"present": {
|
||||
"total": pt[0],
|
||||
"justi": pj[0],
|
||||
},
|
||||
"retard": {
|
||||
"total": rt[0],
|
||||
"justi": rj[0],
|
||||
},
|
||||
"absent": {
|
||||
"total": at[0],
|
||||
"justi": aj[0],
|
||||
}
|
||||
}
|
||||
|
||||
const values = document.querySelector('.stats-values');
|
||||
values.innerHTML = "";
|
||||
|
||||
Object.keys(counter).forEach((key) => {
|
||||
const item = document.createElement('div');
|
||||
item.classList.add('stats-values-item');
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('stats-values-part');
|
||||
|
||||
const heure = document.createElement('span');
|
||||
heure.textContent = `${counter[key].total.heure} heure(s) dont ${counter[key].justi.heure} justifiées`;
|
||||
|
||||
const demi = document.createElement('span');
|
||||
demi.textContent = `${counter[key].total.demi} demi-journée(s) dont ${counter[key].justi.demi} justifiées`;
|
||||
|
||||
const jour = document.createElement('span');
|
||||
jour.textContent = `${counter[key].total.journee} journée(s) dont ${counter[key].justi.journee} justifiées`;
|
||||
|
||||
div.append(jour, demi, heure);
|
||||
|
||||
const title = document.createElement('h5');
|
||||
title.textContent = key.capitalize();
|
||||
|
||||
item.append(title, div)
|
||||
|
||||
values.appendChild(item);
|
||||
});
|
||||
|
||||
const nbAbs = counter.absent.total[assi_metric] - counter.absent.justi[assi_metric];
|
||||
if (nbAbs > assi_seuil) {
|
||||
document.querySelector('.alerte').classList.remove('invisible');
|
||||
document.querySelector('.alerte p').textContent = `Attention, cet étudiant a trop d'absences ${nbAbs} / ${assi_seuil} (${metriques[assi_metric]})`
|
||||
} else {
|
||||
document.querySelector('.alerte').classList.add('invisible');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
function removeAllAssiduites() {
|
||||
|
||||
openPromptModal(
|
||||
"Suppression des assiduités",
|
||||
document.createTextNode(
|
||||
'Souhaitez vous réelement supprimer toutes les assiduités de cet étudiant ? Cette supression est irréversible.')
|
||||
,
|
||||
() => {
|
||||
getAllAssiduitesFromEtud(etudid, (data) => {
|
||||
const toRemove = data.map((a) => a.assiduite_id);
|
||||
console.log(toRemove)
|
||||
deleteAssiduites(toRemove);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
function removeAllJustificatifs() {
|
||||
openPromptModal(
|
||||
"Suppression des justificatifs",
|
||||
document.createTextNode(
|
||||
'Souhaitez vous réelement supprimer tous les justificatifs de cet étudiant ? Cette supression est irréversible.')
|
||||
,
|
||||
() => {
|
||||
getAllJustificatifsFromEtud(etudid, (data) => {
|
||||
const toRemove = data.map((a) => a.justif_id);
|
||||
|
||||
deleteJustificatifs(toRemove);
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Suppression des assiduties
|
||||
*/
|
||||
function deleteAssiduites(assi) {
|
||||
const path = getUrl() + `/api/assiduite/delete`;
|
||||
async_post(
|
||||
path,
|
||||
assi,
|
||||
(data, status) => {
|
||||
//success
|
||||
if (data.success.length > 0) {
|
||||
}
|
||||
location.reload();
|
||||
},
|
||||
(data, status) => {
|
||||
//error
|
||||
console.error(data, status);
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Suppression des justificatifs
|
||||
*/
|
||||
function deleteJustificatifs(justis) {
|
||||
const path = getUrl() + `/api/justificatif/delete`;
|
||||
async_post(
|
||||
path,
|
||||
justis,
|
||||
(data, status) => {
|
||||
//success
|
||||
location.reload();
|
||||
},
|
||||
(data, status) => {
|
||||
//error
|
||||
console.error(data, status);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const metriques = {
|
||||
"heure": "H.",
|
||||
"demi": "1/2 J.",
|
||||
"journee": "J."
|
||||
}
|
||||
|
||||
|
||||
|
||||
const etudid = {{ sco.etud.id }};
|
||||
const assi_metric = "{{ assi_metric | safe }}";
|
||||
const assi_seuil = {{ assi_seuil }};
|
||||
|
||||
const assi_date_debut = "{{date_debut}}";
|
||||
const assi_date_fin = "{{date_fin}}";
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
filterAssiduites = {
|
||||
"columns": [
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"etat",
|
||||
"moduleimpl_id",
|
||||
"est_just"
|
||||
],
|
||||
"filters": {
|
||||
"etat": [
|
||||
"retard",
|
||||
"absent"
|
||||
],
|
||||
"moduleimpl_id": "",
|
||||
"est_just": "false"
|
||||
}
|
||||
};
|
||||
|
||||
filterJustificatifs = {
|
||||
"columns": [
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"etat",
|
||||
"raison",
|
||||
"fichier"
|
||||
],
|
||||
"filters": {
|
||||
"etat": [
|
||||
"attente",
|
||||
"modifie"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('stats_date_fin').value = assi_date_fin;
|
||||
document.getElementById('stats_date_debut').value = assi_date_debut;
|
||||
|
||||
|
||||
|
||||
loadAll();
|
||||
stats();
|
||||
})
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.stats-values-item {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stats {
|
||||
border: 1px solid #333;
|
||||
padding: 5px 2px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.stats-values {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stats-values-item h5 {
|
||||
font-weight: bold;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.stats-values-part {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerte {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border-radius: 7px;
|
||||
|
||||
background-color: crimson;
|
||||
|
||||
}
|
||||
|
||||
.alerte.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alerte p {
|
||||
font-size: larger;
|
||||
color: whitesmoke;
|
||||
|
||||
}
|
||||
|
||||
.suppr {
|
||||
margin: 5px 0;
|
||||
}
|
||||
</style>
|
@ -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);
|
||||
|
@ -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 = "<span class='liste_pagination'><button class='pagination_moins'><</button><select id='paginationAssi'></select><button class='pagination_plus'>></button></span>"
|
||||
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 = "<span class='liste_pagination'><button class='pagination_moins'><</button><select id='paginationJusti'></select><button class='pagination_plus'>></button></span>"
|
||||
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;
|
||||
|
@ -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)
|
||||
|
@ -24,7 +24,7 @@
|
||||
<h2 class="insidebar">Scolarité</h2>
|
||||
<a href="{{url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Semestres</a> <br>
|
||||
<a href="{{url_for('notes.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Programmes</a> <br>
|
||||
<a href="{{url_for('absences.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Absences</a> <br>
|
||||
<a href="{{url_for('assiduites.index_html', scodoc_dept=g.scodoc_dept)}}" class="sidebar">Assiduités</a> <br>
|
||||
|
||||
{% 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</a></li>
|
||||
<li><a href="{{ url_for('assiduites.liste_assiduites_etud', scodoc_dept=g.scodoc_dept,
|
||||
etudid=sco.etud.id) }}">Liste</a></li>
|
||||
<li><a href="{{ url_for('assiduites.bilan_etud', scodoc_dept=g.scodoc_dept,
|
||||
etudid=sco.etud.id) }}">Bilan</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</div> {# /etud-insidebar #}
|
||||
|
@ -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",
|
||||
],
|
||||
),
|
||||
"""<h2>Traitement des assiduités</h2>
|
||||
<p class="help">
|
||||
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).
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
H.append(
|
||||
"""<p class="help">Pour signaler, annuler ou justifier une assiduité pour un seul étudiant,
|
||||
choisissez d'abord concerné:</p>"""
|
||||
choisissez d'abord le concerné:</p>"""
|
||||
)
|
||||
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"""
|
||||
<h2 style="margin-top: 30px;">Billets d'absence</h2>
|
||||
<ul><li><a href="{url_for("absences.list_billets", scodoc_dept=g.scodoc_dept)
|
||||
}">Traitement des billets d'absence en attente</a>
|
||||
</li></ul>
|
||||
"""
|
||||
)
|
||||
# if current_user.has_permission(
|
||||
# Permission.ScoAbsChange
|
||||
# ) and sco_preferences.get_preference("handle_billets_abs"):
|
||||
# H.append(
|
||||
# f"""
|
||||
# <h2 style="margin-top: 30px;">Billets d'absence</h2>
|
||||
# <ul><li><a href="{url_for("absences.list_billets", scodoc_dept=g.scodoc_dept)
|
||||
# }">Traitement des billets d'absence en attente</a>
|
||||
# </li></ul>
|
||||
# """
|
||||
# )
|
||||
|
||||
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'<a href="{url_for("absences.CalAbs", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"])}"><font color="#A00000">{etud["nomprenom"]}</font></a>',
|
||||
"nom": f'<a href="{url_for("assiduites.calendrier_etud", scodoc_dept=g.scodoc_dept, etudid=etud["etudid"])}"><font color="#A00000">{etud["nomprenom"]}</font></a>',
|
||||
"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)
|
||||
|
@ -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(
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user