Assiduites : préférences - métrique + lien assiduité avec reste scodoc

This commit is contained in:
iziram 2023-05-30 15:11:09 +02:00
parent 238b6b10d4
commit 825dc6ecb1
16 changed files with 464 additions and 290 deletions

View File

@ -386,6 +386,12 @@ class BulletinBUT:
semestre_infos["absences"] = { semestre_infos["absences"] = {
"injustifie": nbabs - nbabsjust, "injustifie": nbabs - nbabsjust,
"total": nbabs, "total": nbabs,
"metrique": {
"H.": "Heure(s)",
"J.": "Journée(s)",
"1/2 J.": "1/2 Jour.",
"N.": "Nombre",
}.get(sco_preferences.get_preference("assi_metrique")),
} }
decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {} decisions_ues = self.res.get_etud_decisions_ue(etud.id) or {}
if self.prefs["bul_show_ects"]: if self.prefs["bul_show_ects"]:

View File

@ -39,9 +39,11 @@ from app.models.validations import ScolarFormSemestreValidation
from app.scodoc import codes_cursus, sco_preferences from app.scodoc import codes_cursus, sco_preferences
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import MONTH_NAMES_ABBREV from app.scodoc.sco_utils import MONTH_NAMES_ABBREV, translate_assiduites_metric
from app.scodoc.sco_vdi import ApoEtapeVDI from app.scodoc.sco_vdi import ApoEtapeVDI
from app.scodoc.sco_utils import translate_assiduites_metric
GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024 # bytes GROUPS_AUTO_ASSIGNMENT_DATA_MAX = 1024 * 1024 # bytes
@ -678,8 +680,12 @@ class FormSemestre(db.Model):
""" """
from app.scodoc import sco_abs from app.scodoc import sco_abs
return sco_abs.get_abs_count_in_interval( metrique = sco_preferences.get_preference("assi_metrique", self.id)
etudid, self.date_debut.isoformat(), self.date_fin.isoformat() return sco_abs.get_assiduites_count_in_interval(
etudid,
self.date_debut.isoformat(),
self.date_fin.isoformat(),
translate_assiduites_metric(metrique),
) )
def get_codes_apogee(self, category=None) -> set[str]: def get_codes_apogee(self, category=None) -> set[str]:

View File

@ -116,10 +116,10 @@ def sidebar(etudid: int = None):
) )
if etud["cursem"]: if etud["cursem"]:
cur_sem = etud["cursem"] cur_sem = etud["cursem"]
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, cur_sem) nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, cur_sem)
nbabsnj = nbabs - nbabsjust nbabsnj = nbabs - nbabsjust
H.append( H.append(
f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">(1/2 j.) f"""<span title="absences du { cur_sem["date_debut"] } au { cur_sem["date_fin"] }">({sco_preferences.get_preference("assi_metrique", None)})
<br>{ nbabsjust } J., { nbabsnj } N.J.</span>""" <br>{ nbabsjust } J., { nbabsnj } N.J.</span>"""
) )
H.append("<ul>") H.append("<ul>")

View File

@ -1054,19 +1054,42 @@ def get_abs_count_in_interval(etudid, date_debut_iso, date_fin_iso):
return r return r
def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso): def get_assiduites_count(etudid, sem):
"""Les comptes d'absences de cet étudiant dans ce semestre:
tuple (nb abs non justifiées, nb abs justifiées)
Utilise un cache.
"""
metrique = sco_preferences.get_preference("assi_metrique", sem["formsemestre_id"])
return get_assiduites_count_in_interval(
etudid,
sem["date_debut_iso"],
sem["date_fin_iso"],
scu.translate_assiduites_metric(metrique),
)
def get_assiduites_count_in_interval(
etudid, date_debut_iso, date_fin_iso, metrique="demi"
):
"""Les comptes d'absences de cet étudiant entre ces deux dates, incluses: """Les comptes d'absences de cet étudiant entre ces deux dates, incluses:
tuple (nb abs, nb abs justifiées) tuple (nb abs, nb abs justifiées)
Utilise un cache. Utilise un cache.
""" """
key = str(etudid) + "_" + date_debut_iso + "_" + date_fin_iso + "_assiduites" key = (
str(etudid)
+ "_"
+ date_debut_iso
+ "_"
+ date_fin_iso
+ f"{metrique}_assiduites"
)
r = sco_cache.AbsSemEtudCache.get(key) r = sco_cache.AbsSemEtudCache.get(key)
if not r: if not r:
date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True) date_debut: datetime.datetime = scu.is_iso_formated(date_debut_iso, True)
date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True) date_fin: datetime.datetime = scu.is_iso_formated(date_fin_iso, True)
assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid) assiduites: Assiduite = Assiduite.query.filter_by(etudid=etudid)
assiduites = assiduites.filter(Assiduite.etat != 0)
justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid) justificatifs: Justificatif = Justificatif.query.filter_by(etudid=etudid)
assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin) assiduites = scass.filter_by_date(assiduites, Assiduite, date_debut, date_fin)
@ -1076,7 +1099,7 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
calculator: scass.CountCalculator = scass.CountCalculator() calculator: scass.CountCalculator = scass.CountCalculator()
calculator.compute_assiduites(assiduites) calculator.compute_assiduites(assiduites)
nb_abs: dict = calculator.to_dict()["demi"] nb_abs: dict = calculator.to_dict()[metrique]
abs_just: list[Assiduite] = scass.get_all_justified( abs_just: list[Assiduite] = scass.get_all_justified(
etudid, date_debut, date_fin etudid, date_debut, date_fin
@ -1084,7 +1107,7 @@ def get_assiduites_count_in_interval(etudid, date_debut_iso, date_fin_iso):
calculator.reset() calculator.reset()
calculator.compute_assiduites(abs_just) calculator.compute_assiduites(abs_just)
nb_abs_just: dict = calculator.to_dict()["demi"] nb_abs_just: dict = calculator.to_dict()[metrique]
r = (nb_abs, nb_abs_just) r = (nb_abs, nb_abs_just)
ans = sco_cache.AbsSemEtudCache.set(key, r) ans = sco_cache.AbsSemEtudCache.set(key, r)
@ -1101,6 +1124,15 @@ def invalidate_abs_count(etudid, sem):
sco_cache.AbsSemEtudCache.delete(key) sco_cache.AbsSemEtudCache.delete(key)
def invalidate_assiduites_count(etudid, sem):
"""Invalidate (clear) cached counts"""
date_debut = sem["date_debut_iso"]
date_fin = sem["date_fin_iso"]
for met in ["demi", "journee", "compte", "heure"]:
key = str(etudid) + "_" + date_debut + "_" + date_fin + f"{met}_assiduites"
sco_cache.AbsSemEtudCache.delete(key)
def invalidate_abs_count_sem(sem): def invalidate_abs_count_sem(sem):
"""Invalidate (clear) cached abs counts for all the students of this semestre""" """Invalidate (clear) cached abs counts for all the students of this semestre"""
inscriptions = ( inscriptions = (
@ -1112,6 +1144,17 @@ def invalidate_abs_count_sem(sem):
invalidate_abs_count(ins["etudid"], sem) invalidate_abs_count(ins["etudid"], sem)
def invalidate_assiduites_count_sem(sem):
"""Invalidate (clear) cached abs counts for all the students of this semestre"""
inscriptions = (
sco_formsemestre_inscriptions.do_formsemestre_inscription_listinscrits(
sem["formsemestre_id"]
)
)
for ins in inscriptions:
invalidate_assiduites_count(ins["etudid"], sem)
def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
"""Doit etre appelé à chaque modification des absences pour cet étudiant et cette date. """Doit etre appelé à chaque modification des absences pour cet étudiant et cette date.
Invalide cache absence et caches semestre Invalide cache absence et caches semestre
@ -1145,3 +1188,38 @@ def invalidate_abs_etud_date(etudid, date): # was invalidateAbsEtudDate
# Inval cache compteurs absences: # Inval cache compteurs absences:
invalidate_abs_count_sem(sem) invalidate_abs_count_sem(sem)
def invalidate_assiduites_etud_date(etudid, date):
"""Doit etre appelé à chaque modification des assiduites pour cet étudiant et cette date.
Invalide cache absence et caches semestre
date: date au format ISO
"""
from app.scodoc import sco_compute_moy
# Semestres a cette date:
etud = sco_etud.get_etud_info(etudid=etudid, filled=True)[0]
sems = [
sem
for sem in etud["sems"]
if sem["date_debut_iso"] <= date and sem["date_fin_iso"] >= date
]
# Invalide les PDF et les absences:
for sem in sems:
# Inval cache bulletin et/ou note_table
if sco_compute_moy.formsemestre_expressions_use_abscounts(
sem["formsemestre_id"]
):
# certaines formules utilisent les absences
pdfonly = False
else:
# efface toujours le PDF car il affiche en général les absences
pdfonly = True
sco_cache.invalidate_formsemestre(
formsemestre_id=sem["formsemestre_id"], pdfonly=pdfonly
)
# Inval cache compteurs absences:
invalidate_assiduites_count(etudid, sem)

View File

@ -47,6 +47,7 @@ import app.scodoc.notesdb as ndb
from app.scodoc import sco_etud from app.scodoc import sco_etud
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_users from app.scodoc import sco_users
from app.scodoc import sco_utils as scu
def abs_notify(etudid, date): def abs_notify(etudid, date):
@ -61,8 +62,15 @@ def abs_notify(etudid, date):
if not formsemestre: if not formsemestre:
return # non inscrit a la date, pas de notification return # non inscrit a la date, pas de notification
nbabs, nbabsjust = sco_abs.get_abs_count_in_interval( nbabs, nbabsjust = sco_abs.get_assiduites_count_in_interval(
etudid, formsemestre.date_debut.isoformat(), formsemestre.date_fin.isoformat() etudid,
formsemestre.date_debut.isoformat(),
formsemestre.date_fin.isoformat(),
scu.translate_assiduites_metric(
sco_preferences.get_preference(
"assi_metrique", formsemestre.formsemestre_id
)
),
) )
do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust) do_abs_notify(formsemestre, etudid, date, nbabs, nbabsjust)
@ -85,6 +93,7 @@ def do_abs_notify(formsemestre: FormSemestre, etudid, date, nbabs, nbabsjust):
return # abort return # abort
# Vérification fréquence (pour ne pas envoyer de mails trop souvent) # Vérification fréquence (pour ne pas envoyer de mails trop souvent)
# TODO Mettre la fréquence dans les préférences assiduités
abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq") abs_notify_max_freq = sco_preferences.get_preference("abs_notify_max_freq")
destinations_filtered = [] destinations_filtered = []
for email_addr in destinations: for email_addr in destinations:
@ -174,6 +183,8 @@ def abs_notify_is_above_threshold(etudid, nbabs, nbabsjust, formsemestre_id):
(nbabs > abs_notify_abs_threshold) (nbabs > abs_notify_abs_threshold)
(nbabs - nbabs_last_notified) > abs_notify_abs_increment (nbabs - nbabs_last_notified) > abs_notify_abs_increment
TODO Mettre à jour avec le module assiduité + fonctionnement métrique
""" """
abs_notify_abs_threshold = sco_preferences.get_preference( abs_notify_abs_threshold = sco_preferences.get_preference(
"abs_notify_abs_threshold", formsemestre_id "abs_notify_abs_threshold", formsemestre_id

View File

@ -197,7 +197,7 @@ def formsemestre_bulletinetud_dict(formsemestre_id, etudid, version="long"):
pid = partition["partition_id"] pid = partition["partition_id"]
partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid) partitions_etud_groups[pid] = sco_groups.get_etud_groups_in_partition(pid)
# --- Absences # --- Absences
I["nbabs"], I["nbabsjust"] = sco_abs.get_abs_count(etudid, nt.sem) I["nbabs"], I["nbabsjust"] = sco_abs.get_assiduites_count(etudid, nt.sem)
# --- Decision Jury # --- Decision Jury
infos, dpv = etud_descr_situation_semestre( infos, dpv = etud_descr_situation_semestre(
@ -487,7 +487,7 @@ def _ue_mod_bulletin(
) # peut etre 'NI' ) # peut etre 'NI'
is_malus = mod["module"]["module_type"] == ModuleType.MALUS is_malus = mod["module"]["module_type"] == ModuleType.MALUS
if bul_show_abs_modules: if bul_show_abs_modules:
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
mod_abs = [nbabs, nbabsjust] mod_abs = [nbabs, nbabsjust]
mod["mod_abs_txt"] = scu.fmt_abs(mod_abs) mod["mod_abs_txt"] = scu.fmt_abs(mod_abs)
else: else:

View File

@ -297,7 +297,7 @@ def formsemestre_bulletinetud_published_dict(
# --- Absences # --- Absences
if prefs["bul_show_abs"]: if prefs["bul_show_abs"]:
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust) d["absences"] = dict(nbabs=nbabs, nbabsjust=nbabsjust)
# --- Décision Jury # --- Décision Jury

View File

@ -63,6 +63,7 @@ from app.scodoc import sco_etud
from app.scodoc import sco_xml from app.scodoc import sco_xml
from app.scodoc.sco_xml import quote_xml_attr from app.scodoc.sco_xml import quote_xml_attr
# -------- Bulletin en XML # -------- Bulletin en XML
# (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict() # (fonction séparée: n'utilise pas formsemestre_bulletinetud_dict()
# pour simplifier le code, mais attention a la maintenance !) # pour simplifier le code, mais attention a la maintenance !)
@ -369,7 +370,7 @@ def make_xml_formsemestre_bulletinetud(
# --- Absences # --- Absences
if sco_preferences.get_preference("bul_show_abs", formsemestre_id): if sco_preferences.get_preference("bul_show_abs", formsemestre_id):
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust))) doc.append(Element("absences", nbabs=str(nbabs), nbabsjust=str(nbabsjust)))
# --- Decision Jury # --- Decision Jury
if ( if (

View File

@ -696,7 +696,7 @@ def formsemestre_recap_parcours_table(
f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>""" f"""<td class="rcp_moy">{scu.fmt_note(nt.get_etud_moy_gen(etudid))}</td>"""
) )
# Absences (nb d'abs non just. dans ce semestre) # Absences (nb d'abs non just. dans ce semestre)
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, sem) nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, sem)
H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""") H.append(f"""<td class="rcp_abs">{nbabs - nbabsjust}</td>""")
# UEs # UEs

View File

@ -107,7 +107,7 @@ def etud_get_poursuite_info(sem, etud):
rangs.append(["rang_" + codeModule, rangModule]) rangs.append(["rang_" + codeModule, rangModule])
# Absences # Absences
nbabs, nbabsjust = sco_abs.get_abs_count(etudid, nt.sem) nbabs, nbabsjust = sco_abs.get_assiduites_count(etudid, nt.sem)
if ( if (
dec dec
and not sem_descr # not sem_descr pour ne prendre que le semestre validé le plus récent and not sem_descr # not sem_descr pour ne prendre que le semestre validé le plus récent

View File

@ -653,6 +653,19 @@ class BasePreferences(object):
"explanation": "Liste des jours (lun,mar,mer,jeu,ven,sam,dim)", "explanation": "Liste des jours (lun,mar,mer,jeu,ven,sam,dim)",
}, },
), ),
(
"assi_metrique",
{
"initvalue": "1/2 J.",
"input_type": "menu",
"labels": ["1/2 J.", "J.", "H.", "N."],
"allowed_values": ["1/2 J.", "J.", "H.", "N."],
"title": "Métrique de l'assiduité",
"explanation": "Unité affichée dans la fiche étudiante et le bilan\n(J. = journée, H. = heure, N. = nombre)",
"category": "assi",
"only_global" : True,
},
),
# portal # portal
( (
"portal_url", "portal_url",

View File

@ -139,7 +139,7 @@ def feuille_preparation_jury(formsemestre_id):
main_partition_id, "" main_partition_id, ""
) )
# absences: # absences:
e_nbabs, e_nbabsjust = sco_abs.get_abs_count(etud.id, sem) e_nbabs, e_nbabsjust = sco_abs.get_assiduites_count(etud.id, sem)
nbabs[etud.id] = e_nbabs nbabs[etud.id] = e_nbabs
nbabsjust[etud.id] = e_nbabs - e_nbabsjust nbabsjust[etud.id] = e_nbabs - e_nbabsjust

View File

@ -251,6 +251,17 @@ def is_period_overlapping(
return p_deb < i_fin and p_fin > i_deb return p_deb < i_fin and p_fin > i_deb
def translate_assiduites_metric(hr_metric) -> str:
if hr_metric == "1/2 J.":
return "demi"
if hr_metric == "J.":
return "journee"
if hr_metric == "N.":
return "compte"
if hr_metric == "H.":
return "heure"
# Types de modules # Types de modules
class ModuleType(IntEnum): class ModuleType(IntEnum):
"""Code des types de module.""" """Code des types de module."""

View File

@ -1,70 +1,79 @@
/* Module par Seb. L. */ /* Module par Seb. L. */
class releveBUT extends HTMLElement { class releveBUT extends HTMLElement {
constructor() { constructor() {
super(); super();
this.shadow = this.attachShadow({ mode: 'open' }); this.shadow = this.attachShadow({ mode: "open" });
/* Config par defaut */ /* Config par defaut */
this.config = { this.config = {
showURL: true showURL: true,
}; };
/* Template du module */ /* Template du module */
this.shadow.innerHTML = this.template(); this.shadow.innerHTML = this.template();
/* Style du module */ /* Style du module */
const styles = document.createElement('link'); const styles = document.createElement("link");
styles.setAttribute('rel', 'stylesheet'); styles.setAttribute("rel", "stylesheet");
if (location.href.includes("ScoDoc")) { if (location.href.includes("ScoDoc")) {
styles.setAttribute('href', removeLastTwoComponents(getCurrentScriptPath()) + '/css/releve-but.css'); // Scodoc styles.setAttribute(
} else { "href",
styles.setAttribute('href', '/assets/styles/releve-but.css'); // Passerelle removeLastTwoComponents(getCurrentScriptPath()) + "/css/releve-but.css"
} ); // Scodoc
this.shadow.appendChild(styles); } else {
} styles.setAttribute("href", "/assets/styles/releve-but.css"); // Passerelle
listeOnOff() { }
this.parentElement.parentElement.classList.toggle("listeOff"); this.shadow.appendChild(styles);
this.parentElement.parentElement.querySelectorAll(".moduleOnOff").forEach(e => { }
e.classList.remove("moduleOnOff") listeOnOff() {
}) this.parentElement.parentElement.classList.toggle("listeOff");
} this.parentElement.parentElement
moduleOnOff() { .querySelectorAll(".moduleOnOff")
this.parentElement.classList.toggle("moduleOnOff"); .forEach((e) => {
} e.classList.remove("moduleOnOff");
goTo() { });
let module = this.dataset.module; }
this.parentElement.parentElement.parentElement.parentElement.querySelector("#Module_" + module).scrollIntoView(); moduleOnOff() {
} this.parentElement.classList.toggle("moduleOnOff");
}
goTo() {
let module = this.dataset.module;
this.parentElement.parentElement.parentElement.parentElement
.querySelector("#Module_" + module)
.scrollIntoView();
}
set setConfig(config) { set setConfig(config) {
this.config.showURL = config.showURL ?? this.config.showURL; this.config.showURL = config.showURL ?? this.config.showURL;
} }
set showData(data) { set showData(data) {
// this.showInformations(data); // this.showInformations(data);
this.showSemestre(data); this.showSemestre(data);
this.showSynthese(data); this.showSynthese(data);
this.showEvaluations(data); this.showEvaluations(data);
this.showCustom(data); this.showCustom(data);
this.setOptions(data.options); this.setOptions(data.options);
this.shadow.querySelectorAll(".CTA_Liste").forEach(e => { this.shadow.querySelectorAll(".CTA_Liste").forEach((e) => {
e.addEventListener("click", this.listeOnOff) e.addEventListener("click", this.listeOnOff);
}) });
this.shadow.querySelectorAll(".ue, .module").forEach(e => { this.shadow.querySelectorAll(".ue, .module").forEach((e) => {
e.addEventListener("click", this.moduleOnOff) e.addEventListener("click", this.moduleOnOff);
}) });
this.shadow.querySelectorAll(":not(.ueBonus)+.syntheseModule").forEach(e => { this.shadow
e.addEventListener("click", this.goTo) .querySelectorAll(":not(.ueBonus)+.syntheseModule")
}) .forEach((e) => {
e.addEventListener("click", this.goTo);
});
this.shadow.children[0].classList.add("ready"); this.shadow.children[0].classList.add("ready");
} }
template() { template() {
return ` return `
<div> <div>
<div class="wait"></div> <div class="wait"></div>
<main class="releve"> <main class="releve">
@ -140,33 +149,36 @@ class releveBUT extends HTMLElement {
</main> </main>
</div>`; </div>`;
} }
/********************************/ /********************************/
/* Informations sur l'étudiant */ /* Informations sur l'étudiant */
/********************************/ /********************************/
showInformations(data) { showInformations(data) {
this.shadow.querySelector(".studentPic").src = data.etudiant.photo_url || "default_Student.svg"; this.shadow.querySelector(".studentPic").src =
data.etudiant.photo_url || "default_Student.svg";
let output = ''; let output = "";
if (this.config.showURL) { if (this.config.showURL) {
output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`; output += `<a href="${data.etudiant.fiche_url}" class=info_etudiant>`;
} else { } else {
output += `<div class=info_etudiant>`; output += `<div class=info_etudiant>`;
} }
output += ` output += `
<div class=civilite> <div class=civilite>
${this.civilite(data.etudiant.civilite)} ${this.civilite(data.etudiant.civilite)}
${data.etudiant.nom} ${data.etudiant.nom}
${data.etudiant.prenom}`; ${data.etudiant.prenom}`;
if (data.etudiant.date_naissance) { if (data.etudiant.date_naissance) {
output += ` <div class=dateNaissance>né${(data.etudiant.civilite == "F") ? "e" : ""} le ${this.ISOToDate(data.etudiant.date_naissance)}</div>`; output += ` <div class=dateNaissance>né${
} data.etudiant.civilite == "F" ? "e" : ""
} le ${this.ISOToDate(data.etudiant.date_naissance)}</div>`;
}
output += ` output += `
</div> </div>
<div class=numerosEtudiant> <div class=numerosEtudiant>
Numéro étudiant : ${data.etudiant.code_nip || "~"} - Numéro étudiant : ${data.etudiant.code_nip || "~"} -
@ -174,46 +186,51 @@ class releveBUT extends HTMLElement {
</div> </div>
<div>${data.formation.titre}</div> <div>${data.formation.titre}</div>
`; `;
if (this.config.showURL) { if (this.config.showURL) {
output += `</a>`; output += `</a>`;
} else { } else {
output += `</div>`; output += `</div>`;
} }
this.shadow.querySelector(".infoEtudiant").innerHTML = output; this.shadow.querySelector(".infoEtudiant").innerHTML = output;
} }
/*******************************/ /*******************************/
/* Affichage local */ /* Affichage local */
/*******************************/ /*******************************/
showCustom(data) { showCustom(data) {
this.shadow.querySelector(".custom").innerHTML = data.custom || ""; this.shadow.querySelector(".custom").innerHTML = data.custom || "";
} }
/*******************************/ /*******************************/
/* Information sur le semestre */ /* Information sur le semestre */
/*******************************/ /*******************************/
showSemestre(data) { showSemestre(data) {
let correspondanceCodes = { let correspondanceCodes = {
"ADM": "Admis", ADM: "Admis",
"AJD": "Admis par décision de jury", AJD: "Admis par décision de jury",
"PASD": "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez", PASD: "Passage de droit : tout n'est pas validé, mais d'après les règles du BUT, vous passez",
"PAS1NCI": "Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant", PAS1NCI:
"RED": "Ajourné mais autorisé à redoubler", "Vous passez par décision de jury mais attention, vous n'avez pas partout le niveau suffisant",
"NAR": "Non admis et non autorisé à redoubler : réorientation", RED: "Ajourné mais autorisé à redoubler",
"DEM": "Démission", NAR: "Non admis et non autorisé à redoubler : réorientation",
"ABAN": "Abandon constaté sans lettre de démission", DEM: "Démission",
"RAT": "En attente d'un rattrapage", ABAN: "Abandon constaté sans lettre de démission",
"EXCLU": "Exclusion dans le cadre d'une décision disciplinaire", RAT: "En attente d'un rattrapage",
"DEF": "Défaillance : non évalué par manque d'assiduité", EXCLU: "Exclusion dans le cadre d'une décision disciplinaire",
"ABL": "Année blanche" DEF: "Défaillance : non évalué par manque d'assiduité",
} ABL: "Année blanche",
};
this.shadow.querySelector("#identite_etudiant").innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `; this.shadow.querySelector(
this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(data.semestre.inscription); "#identite_etudiant"
let output = ''; ).innerHTML = ` <a href="${data.etudiant.fiche_url}">${data.etudiant.nomprenom}</a> `;
if (!data.options.block_moyenne_generale) { this.shadow.querySelector(".dateInscription").innerHTML += this.ISOToDate(
output += ` data.semestre.inscription
);
let output = "";
if (!data.options.block_moyenne_generale) {
output += `
<div> <div>
<div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div> <div class=enteteSemestre>Moyenne</div><div class=enteteSemestre>${data.semestre.notes.value}</div>
<div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div> <div class=rang>Rang :</div><div class=rang>${data.semestre.rang.value} / ${data.semestre.rang.total}</div>
@ -222,64 +239,72 @@ class releveBUT extends HTMLElement {
<div>Min. promo. :</div><div>${data.semestre.notes.min}</div> <div>Min. promo. :</div><div>${data.semestre.notes.min}</div>
</div> </div>
`; `;
} }
output += ` output += `
${(() => { ${(() => {
if ((!data.semestre.rang.groupes) || if (
(Object.keys(data.semestre.rang.groupes).length == 0)) { !data.semestre.rang.groupes ||
return ""; Object.keys(data.semestre.rang.groupes).length == 0
} ) {
let output = ""; return "";
let [idGroupe, dataGroupe] = Object.entries(data.semestre.rang.groupes)[0]; }
output += `<div> let output = "";
let [idGroupe, dataGroupe] = Object.entries(
data.semestre.rang.groupes
)[0];
output += `<div>
<div class=enteteSemestre>${data.semestre.groupes[0]?.group_name}</div><div></div> <div class=enteteSemestre>${data.semestre.groupes[0]?.group_name}</div><div></div>
<div class=rang>Rang :</div><div class=rang>${dataGroupe.value} / ${dataGroupe.total}</div> <div class=rang>Rang :</div><div class=rang>${dataGroupe.value} / ${dataGroupe.total}</div>
</div>`; </div>`;
// <div>Max. promo. :</div><div>${dataGroupe.max || "-"}</div> // <div>Max. promo. :</div><div>${dataGroupe.max || "-"}</div>
// <div>Moy. promo. :</div><div>${dataGroupe.moy || "-"}</div> // <div>Moy. promo. :</div><div>${dataGroupe.moy || "-"}</div>
// <div>Min. promo. :</div><div>${dataGroupe.min || "-"}</div> // <div>Min. promo. :</div><div>${dataGroupe.min || "-"}</div>
return output; return output;
})()} })()}
<div class=absencesRecap> <div class=absencesRecap>
<div class=enteteSemestre>Absences</div><div class=enteteSemestre>1/2 jour.</div> <div class=enteteSemestre>Absences</div><div class=enteteSemestre>${
data.semestre.absences?.metrique ?? "1/2 jour."
}</div>
<div class=abs>Non justifiées</div> <div class=abs>Non justifiées</div>
<div>${data.semestre.absences?.injustifie ?? "-"}</div> <div>${data.semestre.absences?.injustifie ?? "-"}</div>
<div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div> <div class=abs>Total</div><div>${data.semestre.absences?.total ?? "-"}</div>
</div>`; </div>`;
if (data.semestre.decision_rcue?.length) { if (data.semestre.decision_rcue?.length) {
output += ` output += `
<div> <div>
<div class=enteteSemestre>RCUE</div><div></div> <div class=enteteSemestre>RCUE</div><div></div>
${(() => { ${(() => {
let output = ""; let output = "";
data.semestre.decision_rcue.forEach(competence => { data.semestre.decision_rcue.forEach((competence) => {
output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`; output += `<div class=competence>${competence.niveau.competence.titre}</div><div>${competence.code}</div>`;
}) });
return output; return output;
})()} })()}
</div> </div>
</div>` </div>`;
} }
if (data.semestre.decision_ue?.length) { if (data.semestre.decision_ue?.length) {
output += ` output += `
<div> <div>
<div class=enteteSemestre>UE</div><div></div> <div class=enteteSemestre>UE</div><div></div>
${(() => { ${(() => {
let output = ""; let output = "";
data.semestre.decision_ue.forEach(ue => { data.semestre.decision_ue.forEach((ue) => {
output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`; output += `<div class=competence>${ue.acronyme}</div><div>${ue.code}</div>`;
}) });
return output; return output;
})()} })()}
</div> </div>
</div>` </div>`;
} }
output += ` output += `
<a class=photo href="${data.etudiant.fiche_url}"> <a class=photo href="${data.etudiant.fiche_url}">
<img src="${data.etudiant.photo_url || "default_Student.svg"}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0"> <img src="${
data.etudiant.photo_url || "default_Student.svg"
}" alt="photo de l'étudiant" title="fiche de l'étudiant" height="120" border="0">
</a>`; </a>`;
/*${data.semestre.groupes.map(groupe => { /*${data.semestre.groupes.map(groupe => {
return ` return `
<div> <div>
<div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div> <div class=enteteSemestre>Groupe</div><div class=enteteSemestre>${groupe.nom}</div>
@ -291,37 +316,42 @@ class releveBUT extends HTMLElement {
`; `;
}).join("") }).join("")
}*/ }*/
this.shadow.querySelector(".infoSemestre").innerHTML = output; this.shadow.querySelector(".infoSemestre").innerHTML = output;
/*if(data.semestre.decision_annee?.code){
/*if(data.semestre.decision_annee?.code){
this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code]; this.shadow.querySelector(".decision_annee").innerHTML = "Décision année : " + data.semestre.decision_annee.code + " - " + correspondanceCodes[data.semestre.decision_annee.code];
}*/ }*/
this.shadow.querySelector(".decision").innerHTML = data.semestre.situation || ""; this.shadow.querySelector(".decision").innerHTML =
/*if (data.semestre.decision?.code) { data.semestre.situation || "";
/*if (data.semestre.decision?.code) {
this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || ""); this.shadow.querySelector(".decision").innerHTML = "Décision jury: " + (data.semestre.decision?.code || "");
}*/ }*/
this.shadow.querySelector("#ects_tot").innerHTML = "ECTS&nbsp;:&nbsp;" + (data.semestre.ECTS?.acquis ?? "-") + "&nbsp;/&nbsp;" + (data.semestre.ECTS?.total ?? "-"); this.shadow.querySelector("#ects_tot").innerHTML =
} "ECTS&nbsp;:&nbsp;" +
(data.semestre.ECTS?.acquis ?? "-") +
"&nbsp;/&nbsp;" +
(data.semestre.ECTS?.total ?? "-");
}
/*******************************/ /*******************************/
/* Synthèse */ /* Synthèse */
/*******************************/ /*******************************/
showSynthese(data) { showSynthese(data) {
let output = ``; let output = ``;
/* Fusion et tri des UE et UE capitalisées */ /* Fusion et tri des UE et UE capitalisées */
let fusionUE = [ let fusionUE = [
...Object.entries(data.ues), ...Object.entries(data.ues),
...Object.entries(data.ues_capitalisees) ...Object.entries(data.ues_capitalisees),
].sort((a, b) => { ].sort((a, b) => {
return a[1].numero - b[1].numero return a[1].numero - b[1].numero;
}); });
/* Affichage */ /* Affichage */
fusionUE.forEach(([ue, dataUE]) => { fusionUE.forEach(([ue, dataUE]) => {
if (dataUE.type == 1) { // UE Sport / Bonus if (dataUE.type == 1) {
output += ` // UE Sport / Bonus
output += `
<div> <div>
<div class="ue ueBonus"> <div class="ue ueBonus">
<h3>Bonus</h3> <h3>Bonus</h3>
@ -330,52 +360,60 @@ class releveBUT extends HTMLElement {
${this.ueSport(dataUE.modules)} ${this.ueSport(dataUE.modules)}
</div> </div>
`; `;
} else { } else {
output += ` output += `
<div> <div>
<div class="ue ${dataUE.date_capitalisation ? "capitalisee" : ""}"> <div class="ue ${dataUE.date_capitalisation ? "capitalisee" : ""}">
<h3> <h3>
${ue}${(dataUE.titre) ? " - " + dataUE.titre : ""} ${ue}${dataUE.titre ? " - " + dataUE.titre : ""}
</h3> </h3>
<div> <div>
<div class=moyenne>Moyenne&nbsp;:&nbsp;${dataUE.moyenne?.value || dataUE.moyenne || "-"}</div> <div class=moyenne>Moyenne&nbsp;:&nbsp;${
<div class=ue_rang>Rang&nbsp;:&nbsp;${dataUE.moyenne?.rang}&nbsp;/&nbsp;${dataUE.moyenne?.total}</div> dataUE.moyenne?.value || dataUE.moyenne || "-"
}</div>
<div class=ue_rang>Rang&nbsp;:&nbsp;${dataUE.moyenne?.rang}&nbsp;/&nbsp;${
dataUE.moyenne?.total
}</div>
<div class=info>`; <div class=info>`;
if (!dataUE.date_capitalisation) { if (!dataUE.date_capitalisation) {
output += ` Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;- output += ` Bonus&nbsp;:&nbsp;${dataUE.bonus || 0}&nbsp;-
Malus&nbsp;:&nbsp;${dataUE.malus || 0}`; Malus&nbsp;:&nbsp;${dataUE.malus || 0}`;
} else { } else {
output += ` le ${this.ISOToDate(dataUE.date_capitalisation.split("T")[0])} <a href="${dataUE.bul_orig_url}">dans ce semestre</a>`; output += ` le ${this.ISOToDate(
} dataUE.date_capitalisation.split("T")[0]
)} <a href="${dataUE.bul_orig_url}">dans ce semestre</a>`;
}
output += ` <span class=ects>&nbsp;- output += ` <span class=ects>&nbsp;-
ECTS&nbsp;:&nbsp;${dataUE.ECTS?.acquis ?? "-"}&nbsp;/&nbsp;${dataUE.ECTS?.total ?? "-"} ECTS&nbsp;:&nbsp;${dataUE.ECTS?.acquis ?? "-"}&nbsp;/&nbsp;${
dataUE.ECTS?.total ?? "-"
}
</span> </span>
</div> </div>
</div>`; </div>`;
/*<div class=absences> /*<div class=absences>
<div>Abs&nbsp;N.J.</div><div>${dataUE.absences?.injustifie || 0}</div> <div>Abs&nbsp;N.J.</div><div>${dataUE.absences?.injustifie || 0}</div>
<div>Total</div><div>${dataUE.absences?.total || 0}</div> <div>Total</div><div>${dataUE.absences?.total || 0}</div>
</div>*/ </div>*/
output += "</div>"; output += "</div>";
if (!dataUE.date_capitalisation) { if (!dataUE.date_capitalisation) {
output += output +=
this.synthese(data, dataUE.ressources) + this.synthese(data, dataUE.ressources) +
this.synthese(data, dataUE.saes); this.synthese(data, dataUE.saes);
} }
output += "</div>"; output += "</div>";
} }
}); });
this.shadow.querySelector(".synthese").innerHTML = output; this.shadow.querySelector(".synthese").innerHTML = output;
} }
synthese(data, modules) { synthese(data, modules) {
let output = ""; let output = "";
Object.entries(modules).forEach(([module, dataModule]) => { Object.entries(modules).forEach(([module, dataModule]) => {
let titre = data.ressources[module]?.titre || data.saes[module]?.titre; let titre = data.ressources[module]?.titre || data.saes[module]?.titre;
//let url = data.ressources[module]?.url || data.saes[module]?.url; //let url = data.ressources[module]?.url || data.saes[module]?.url;
output += ` output += `
<div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}"> <div class=syntheseModule data-module="${module.replace(/[^a-zA-Z0-9]/g, "")}">
<div>${module}&nbsp;- ${titre}</div> <div>${module}&nbsp;- ${titre}</div>
<div> <div>
@ -384,14 +422,14 @@ class releveBUT extends HTMLElement {
</div> </div>
</div> </div>
`; `;
}) });
return output; return output;
} }
ueSport(modules) { ueSport(modules) {
let output = ""; let output = "";
Object.values(modules).forEach((module) => { Object.values(modules).forEach((module) => {
Object.values(module.evaluations).forEach((evaluation) => { Object.values(module.evaluations).forEach((evaluation) => {
output += ` output += `
<div class=syntheseModule> <div class=syntheseModule>
<div>${module.titre} - ${evaluation.description || "Note"}</div> <div>${module.titre} - ${evaluation.description || "Note"}</div>
<div> <div>
@ -400,27 +438,31 @@ class releveBUT extends HTMLElement {
</div> </div>
</div> </div>
`; `;
}) });
}) });
return output; return output;
} }
/*******************************/ /*******************************/
/* Evaluations */ /* Evaluations */
/*******************************/ /*******************************/
showEvaluations(data) { showEvaluations(data) {
this.shadow.querySelector(".evaluations").innerHTML = this.module(data.ressources); this.shadow.querySelector(".evaluations").innerHTML = this.module(
this.shadow.querySelector(".sae").innerHTML += this.module(data.saes); data.ressources
} );
module(module) { this.shadow.querySelector(".sae").innerHTML += this.module(data.saes);
let output = ""; }
Object.entries(module).forEach(([numero, content]) => { module(module) {
output += ` let output = "";
Object.entries(module).forEach(([numero, content]) => {
output += `
<div id="Module_${numero.replace(/[^a-zA-Z0-9]/g, "")}"> <div id="Module_${numero.replace(/[^a-zA-Z0-9]/g, "")}">
<div class=module> <div class=module>
<h3>${this.URL(content.url, `${numero} - ${content.titre}`)}</h3> <h3>${this.URL(content.url, `${numero} - ${content.titre}`)}</h3>
<div> <div>
<div class=moyenne>Moyenne&nbsp;indicative&nbsp;:&nbsp;${content.moyenne.value}</div> <div class=moyenne>Moyenne&nbsp;indicative&nbsp;:&nbsp;${
content.moyenne.value
}</div>
<div class=info> <div class=info>
Classe&nbsp;:&nbsp;${content.moyenne.moy}&nbsp;- Classe&nbsp;:&nbsp;${content.moyenne.moy}&nbsp;-
Max&nbsp;:&nbsp;${content.moyenne.max}&nbsp;- Max&nbsp;:&nbsp;${content.moyenne.max}&nbsp;-
@ -435,14 +477,14 @@ class releveBUT extends HTMLElement {
${this.evaluation(content.evaluations)} ${this.evaluation(content.evaluations)}
</div> </div>
`; `;
}) });
return output; return output;
} }
evaluation(evaluations) { evaluation(evaluations) {
let output = ""; let output = "";
evaluations.forEach((evaluation) => { evaluations.forEach((evaluation) => {
output += ` output += `
<div class=eval> <div class=eval>
<div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div> <div>${this.URL(evaluation.url, evaluation.description || "Évaluation")}</div>
<div> <div>
@ -454,52 +496,55 @@ class releveBUT extends HTMLElement {
<div>Max. promo.</div><div>${evaluation.note.max}</div> <div>Max. promo.</div><div>${evaluation.note.max}</div>
<div>Moy. promo.</div><div>${evaluation.note.moy}</div> <div>Moy. promo.</div><div>${evaluation.note.moy}</div>
<div>Min. promo.</div><div>${evaluation.note.min}</div> <div>Min. promo.</div><div>${evaluation.note.min}</div>
${Object.entries(evaluation.poids).map(([UE, poids]) => { ${Object.entries(evaluation.poids)
return ` .map(([UE, poids]) => {
return `
<div>Poids ${UE}</div> <div>Poids ${UE}</div>
<div>${poids}</div> <div>${poids}</div>
`; `;
}).join("")} })
.join("")}
</div> </div>
</div> </div>
`; `;
}) });
return output; return output;
} }
/********************/ /********************/
/* Options */ /* Options */
/********************/ /********************/
setOptions(options) { setOptions(options) {
Object.entries(options).forEach(([option, value]) => { Object.entries(options).forEach(([option, value]) => {
if (value === false) { if (value === false) {
this.shadow.children[0].classList.add(option.replace("show", "hide")); this.shadow.children[0].classList.add(option.replace("show", "hide"));
} }
}); });
} }
/********************/
/* Fonctions d'aide */
/********************/
URL(href, content) {
if (this.config.showURL) {
return `<a href=${href}>${content}</a>`;
} else {
return content;
}
}
civilite(txt) {
switch (txt) {
case "M":
return "M.";
case "F":
return "Mme";
default:
return "";
}
}
/********************/ ISOToDate(ISO) {
/* Fonctions d'aide */ return ISO.split("-").reverse().join("/");
/********************/ }
URL(href, content) {
if (this.config.showURL) {
return `<a href=${href}>${content}</a>`;
} else {
return content;
}
}
civilite(txt) {
switch (txt) {
case "M": return "M.";
case "F": return "Mme";
default: return "";
}
}
ISOToDate(ISO) {
return ISO.split("-").reverse().join("/");
}
} }
customElements.define('releve-but', releveBUT); customElements.define("releve-but", releveBUT);

View File

@ -55,7 +55,7 @@
<b>Absences</b> <b>Absences</b>
{% if sco.etud_cur_sem %} {% if sco.etud_cur_sem %}
<span title="absences du {{ sco.etud_cur_sem['date_debut'] }} <span title="absences du {{ sco.etud_cur_sem['date_debut'] }}
au {{ sco.etud_cur_sem['date_fin'] }}">(1/2 j.) au {{ sco.etud_cur_sem['date_fin'] }}">({{sco.prefs["assi_metrique"]}})
<br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span> <br />{{sco.nbabsjust}} J., {{sco.nbabsnj}} N.J.</span>
{% endif %} {% endif %}
<ul> <ul>

View File

@ -72,10 +72,13 @@ class ScoData:
ins = self.etud.inscription_courante() ins = self.etud.inscription_courante()
if ins: if ins:
self.etud_cur_sem = ins.formsemestre self.etud_cur_sem = ins.formsemestre
self.nbabs, self.nbabsjust = sco_abs.get_abs_count_in_interval( self.nbabs, self.nbabsjust = sco_abs.get_assiduites_count_in_interval(
etud.id, etud.id,
self.etud_cur_sem.date_debut.isoformat(), self.etud_cur_sem.date_debut.isoformat(),
self.etud_cur_sem.date_fin.isoformat(), self.etud_cur_sem.date_fin.isoformat(),
scu.translate_assiduites_metric(
sco_preferences.get_preference("assi_metrique")
),
) )
self.nbabsnj = self.nbabs - self.nbabsjust self.nbabsnj = self.nbabs - self.nbabsjust
else: else: