forked from ScoDoc/ScoDoc
Update opolka/ScoDoc from ScoDoc/ScoDoc #2
@ -5,7 +5,6 @@ from datetime import datetime
|
|||||||
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_sqlalchemy.query import Query
|
from flask_sqlalchemy.query import Query
|
||||||
from psycopg2.errors import InvalidTextRepresentation # c'est ok
|
|
||||||
from sqlalchemy.exc import DataError
|
from sqlalchemy.exc import DataError
|
||||||
|
|
||||||
from app import db, log, g, set_sco_dept
|
from app import db, log, g, set_sco_dept
|
||||||
@ -256,43 +255,19 @@ class Assiduite(ScoDocModel):
|
|||||||
|
|
||||||
def set_moduleimpl(self, moduleimpl_id: int | str):
|
def set_moduleimpl(self, moduleimpl_id: int | str):
|
||||||
"""Mise à jour du moduleimpl_id
|
"""Mise à jour du moduleimpl_id
|
||||||
Les valeurs du champs "moduleimpl_id" possibles sont :
|
Les valeurs du champ "moduleimpl_id" possibles sont :
|
||||||
- <int> (un id classique)
|
- <int> (un id classique)
|
||||||
- <str> ("autre" ou "<id>")
|
- <str> ("autre" ou "<id>")
|
||||||
- None (pas de moduleimpl_id)
|
- "" (pas de moduleimpl_id)
|
||||||
Si la valeur est "autre" il faut:
|
Si la valeur est "autre" il faut:
|
||||||
- mettre à None assiduité.moduleimpl_id
|
- mettre à None assiduité.moduleimpl_id
|
||||||
- mettre à jour assiduite.external_data["module"] = "autre"
|
- mettre à jour assiduite.external_data["module"] = "autre"
|
||||||
En fonction de la configuration du semestre la valeur `None` peut-être considérée comme invalide.
|
En fonction de la configuration du semestre (option force_module) la valeur "" peut-être
|
||||||
|
considérée comme invalide.
|
||||||
- Il faudra donc vérifier que ce n'est pas le cas avant de mettre à jour l'assiduité
|
- Il faudra donc vérifier que ce n'est pas le cas avant de mettre à jour l'assiduité
|
||||||
"""
|
"""
|
||||||
moduleimpl: ModuleImpl = None
|
moduleimpl: ModuleImpl = None
|
||||||
try:
|
if moduleimpl_id == "autre":
|
||||||
# ne lève une erreur que si moduleimpl_id est une chaine de caractère non parsable (parseInt)
|
|
||||||
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
|
||||||
# moduleImpl est soit :
|
|
||||||
# - None si moduleimpl_id==None
|
|
||||||
# - None si moduleimpl_id==<int> non reconnu
|
|
||||||
# - ModuleImpl si <int|str> valide
|
|
||||||
|
|
||||||
# Vérification ModuleImpl not None (raise ScoValueError)
|
|
||||||
if moduleimpl is None and self._check_force_module(moduleimpl):
|
|
||||||
# Ici uniquement si on est autorisé à ne pas avoir de module
|
|
||||||
self.moduleimpl_id = None
|
|
||||||
return
|
|
||||||
|
|
||||||
# Vérification Inscription ModuleImpl (raise ScoValueError)
|
|
||||||
if moduleimpl.est_inscrit(self.etudiant):
|
|
||||||
self.moduleimpl_id = moduleimpl.id
|
|
||||||
else:
|
|
||||||
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
|
||||||
|
|
||||||
except (DataError, InvalidTextRepresentation) as exc:
|
|
||||||
# On arrive ici si moduleimpl_id == "autre" ou moduleimpl_id == <str> non parsé
|
|
||||||
|
|
||||||
if moduleimpl_id != "autre":
|
|
||||||
raise ScoValueError("Module non reconnu") from exc
|
|
||||||
|
|
||||||
# Configuration de external_data pour Module Autre
|
# Configuration de external_data pour Module Autre
|
||||||
# Si self.external_data None alors on créé un dictionnaire {"module": "autre"}
|
# Si self.external_data None alors on créé un dictionnaire {"module": "autre"}
|
||||||
# Sinon on met à jour external_data["module"] à "autre"
|
# Sinon on met à jour external_data["module"] à "autre"
|
||||||
@ -306,6 +281,29 @@ class Assiduite(ScoDocModel):
|
|||||||
self.moduleimpl_id = None
|
self.moduleimpl_id = None
|
||||||
|
|
||||||
# Ici pas de vérification du force module car on l'a mis dans "external_data"
|
# Ici pas de vérification du force module car on l'a mis dans "external_data"
|
||||||
|
return
|
||||||
|
|
||||||
|
if moduleimpl_id != "":
|
||||||
|
try:
|
||||||
|
moduleimpl_id = int(moduleimpl_id)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ScoValueError("Module non reconnu") from exc
|
||||||
|
moduleimpl: ModuleImpl = ModuleImpl.query.get(moduleimpl_id)
|
||||||
|
|
||||||
|
# ici moduleimpl est None si non spécifié
|
||||||
|
|
||||||
|
# Vérification ModuleImpl not None (raise ScoValueError)
|
||||||
|
if moduleimpl is None:
|
||||||
|
self._check_force_module()
|
||||||
|
# Ici uniquement si on est autorisé à ne pas avoir de module
|
||||||
|
self.moduleimpl_id = None
|
||||||
|
return
|
||||||
|
|
||||||
|
# Vérification Inscription ModuleImpl (raise ScoValueError)
|
||||||
|
if moduleimpl.est_inscrit(self.etudiant):
|
||||||
|
self.moduleimpl_id = moduleimpl.id
|
||||||
|
else:
|
||||||
|
raise ScoValueError("L'étudiant n'est pas inscrit au module")
|
||||||
|
|
||||||
def supprime(self):
|
def supprime(self):
|
||||||
"Supprime l'assiduité. Log et commit."
|
"Supprime l'assiduité. Log et commit."
|
||||||
@ -335,7 +333,7 @@ class Assiduite(ScoDocModel):
|
|||||||
return get_formsemestre_from_data(self.to_dict())
|
return get_formsemestre_from_data(self.to_dict())
|
||||||
|
|
||||||
def get_module(self, traduire: bool = False) -> int | str:
|
def get_module(self, traduire: bool = False) -> int | str:
|
||||||
"TODO"
|
"TODO documenter"
|
||||||
if self.moduleimpl_id is not None:
|
if self.moduleimpl_id is not None:
|
||||||
if traduire:
|
if traduire:
|
||||||
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
modimpl: ModuleImpl = ModuleImpl.query.get(self.moduleimpl_id)
|
||||||
@ -364,8 +362,12 @@ class Assiduite(ScoDocModel):
|
|||||||
|
|
||||||
return f"saisie le {date} {utilisateur}"
|
return f"saisie le {date} {utilisateur}"
|
||||||
|
|
||||||
def _check_force_module(self, moduleimpl: ModuleImpl) -> bool:
|
def _check_force_module(self):
|
||||||
# Vérification si module forcé
|
"""Vérification si module forcé:
|
||||||
|
Si le module est requis, raise ScoValueError
|
||||||
|
sinon ne fait rien.
|
||||||
|
"""
|
||||||
|
# cherche le formsemestre affecté pour utiliser ses préférences
|
||||||
formsemestre: FormSemestre = get_formsemestre_from_data(
|
formsemestre: FormSemestre = get_formsemestre_from_data(
|
||||||
{
|
{
|
||||||
"etudid": self.etudid,
|
"etudid": self.etudid,
|
||||||
@ -373,18 +375,15 @@ class Assiduite(ScoDocModel):
|
|||||||
"date_fin": self.date_fin,
|
"date_fin": self.date_fin,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
force: bool
|
formsemestre_id = formsemestre.id if formsemestre else None
|
||||||
|
# si pas de formsemestre, utilisera les prefs globales du département
|
||||||
if formsemestre:
|
dept_id = self.etudiant.dept_id
|
||||||
force = is_assiduites_module_forced(formsemestre_id=formsemestre.id)
|
force = is_assiduites_module_forced(
|
||||||
else:
|
formsemestre_id=formsemestre_id, dept_id=dept_id
|
||||||
force = is_assiduites_module_forced(dept_id=self.etudiant.dept_id)
|
)
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
raise ScoValueError("Module non renseigné")
|
raise ScoValueError("Module non renseigné")
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class Justificatif(ScoDocModel):
|
class Justificatif(ScoDocModel):
|
||||||
"""
|
"""
|
||||||
|
@ -419,7 +419,9 @@ def moduleimpl_inscriptions_stats(formsemestre_id):
|
|||||||
for info in ues_cap_info[ue["ue_id"]]:
|
for info in ues_cap_info[ue["ue_id"]]:
|
||||||
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
etud = sco_etud.get_etud_info(etudid=info["etudid"], filled=True)[0]
|
||||||
H.append(
|
H.append(
|
||||||
f"""<li class="etud"><a class="discretelink" href="{
|
f"""<li class="etud"><a class="discretelink etudinfo"
|
||||||
|
id="{info['etudid']}"
|
||||||
|
href="{
|
||||||
url_for(
|
url_for(
|
||||||
"scolar.fiche_etud",
|
"scolar.fiche_etud",
|
||||||
scodoc_dept=g.scodoc_dept,
|
scodoc_dept=g.scodoc_dept,
|
||||||
|
@ -147,6 +147,7 @@ def get_preference(name, formsemestre_id=None, dept_id=None):
|
|||||||
"""Returns value of named preference.
|
"""Returns value of named preference.
|
||||||
All preferences have a sensible default value, so this
|
All preferences have a sensible default value, so this
|
||||||
function always returns a usable value for all defined preferences names.
|
function always returns a usable value for all defined preferences names.
|
||||||
|
If dept_id is None, use current dept (g.scodoc_dept_id)
|
||||||
"""
|
"""
|
||||||
return get_base_preferences(dept_id=dept_id).get(formsemestre_id, name)
|
return get_base_preferences(dept_id=dept_id).get(formsemestre_id, name)
|
||||||
|
|
||||||
|
@ -1630,20 +1630,12 @@ def is_entreprises_enabled():
|
|||||||
def is_assiduites_module_forced(
|
def is_assiduites_module_forced(
|
||||||
formsemestre_id: int = None, dept_id: int = None
|
formsemestre_id: int = None, dept_id: int = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
"""Vrai si préférence "imposer la saisie du module" sur les assiduités est vraie."""
|
||||||
from app.scodoc import sco_preferences
|
from app.scodoc import sco_preferences
|
||||||
|
|
||||||
retour: bool
|
return sco_preferences.get_preference(
|
||||||
|
"forcer_module", formsemestre_id=formsemestre_id, dept_id=dept_id
|
||||||
if dept_id is None:
|
)
|
||||||
dept_id = g.scodoc_dept_id
|
|
||||||
|
|
||||||
try:
|
|
||||||
retour = sco_preferences.get_preference(
|
|
||||||
"forcer_module", formsemestre_id=int(formsemestre_id)
|
|
||||||
)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id)
|
|
||||||
return retour
|
|
||||||
|
|
||||||
|
|
||||||
def get_assiduites_time_config(config_type: str) -> str | int:
|
def get_assiduites_time_config(config_type: str) -> str | int:
|
||||||
|
@ -1,235 +0,0 @@
|
|||||||
{% include "assiduites/widgets/toast.j2" %}
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
|
|
||||||
{% block pageContent %}
|
|
||||||
<div class="pageContent">
|
|
||||||
<h3>Signaler une absence, présence ou retard pour {{etud.html_link_fiche()|safe}}</h3>
|
|
||||||
{% if saisie_eval %}
|
|
||||||
<div id="saisie_eval">
|
|
||||||
<br>
|
|
||||||
<h3>
|
|
||||||
La saisie a été préconfigurée en fonction de l'évaluation. <br>
|
|
||||||
Une fois la saisie terminée, cliquez sur le lien ci-dessous
|
|
||||||
</h3>
|
|
||||||
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<section class="assi-form page">
|
|
||||||
<fieldset>
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="assi_date_debut" required>Date de début</legend>
|
|
||||||
<input type="text" name="assi_date_debut" id="assi_date_debut" size="10"
|
|
||||||
class="datepicker">
|
|
||||||
<input type="text" name="assi_heure_debut" id="assi_heure_debut" size="5"
|
|
||||||
class="timepicker">
|
|
||||||
<span>Journée entière</span> <input type="checkbox" name="assi_journee" id="assi_journee">
|
|
||||||
</div>
|
|
||||||
<div class="assi-label" id="date_fin">
|
|
||||||
<legend for="assi_date_fin" required>Date de fin</legend>
|
|
||||||
<scodoc-datetime name="assi_date_fin" id="assi_date_fin"></scodoc-datetime>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="assi_etat" required>État de l'assiduité</legend>
|
|
||||||
<select name="assi_etat" id="assi_etat">
|
|
||||||
<option value="absent" selected>Absent</option>
|
|
||||||
<option value="retard">Retard</option>
|
|
||||||
<option value="present">Présent</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="assi_module" required>Module</legend>
|
|
||||||
{% with moduleid="ajout_assiduite_module_impl",label=false %}
|
|
||||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="assi-row">
|
|
||||||
<div class="assi-label">
|
|
||||||
<legend for="raison">Raison</legend>
|
|
||||||
<textarea name="raison" id="raison" cols="75" rows="4" maxlength="500"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="assi-row">
|
|
||||||
<button onclick="validerFormulaire(this)">Enregistrer</button>
|
|
||||||
<button onclick="effacerFormulaire()">Remettre à zero</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<section class="assi-liste">
|
|
||||||
{{tableau | safe }}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.assi-row {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assi-form fieldset {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pageContent {
|
|
||||||
max-width: var(--sco-content-max-width);
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.assi-label {
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[required]::after {
|
|
||||||
content: "*";
|
|
||||||
color: var(--color-error);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% include "sco_timepicker.j2" %}
|
|
||||||
<script>
|
|
||||||
function validateFields() {
|
|
||||||
const field = document.querySelector('.assi-form')
|
|
||||||
const { deb, fin } = getDates()
|
|
||||||
const date_debut = new Date(deb);
|
|
||||||
const date_fin = new Date(fin);
|
|
||||||
|
|
||||||
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
|
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (date_fin.isBefore(date_debut)) {
|
|
||||||
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldsToAssiduite() {
|
|
||||||
const field = document.querySelector('.assi-form.page')
|
|
||||||
|
|
||||||
const { deb, fin } = getDates()
|
|
||||||
|
|
||||||
const etat = field.querySelector('#assi_etat').value;
|
|
||||||
const raison = field.querySelector('#raison').value;
|
|
||||||
const module = field.querySelector("#ajout_assiduite_module_impl").value;
|
|
||||||
|
|
||||||
return {
|
|
||||||
date_debut: new Date(deb).toFakeIso(),
|
|
||||||
date_fin: new Date(fin).toFakeIso(),
|
|
||||||
etat: etat,
|
|
||||||
description: raison,
|
|
||||||
moduleimpl_id: module,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validerFormulaire(btn) {
|
|
||||||
if (!validateFields()) return
|
|
||||||
|
|
||||||
const assiduite = fieldsToAssiduite();
|
|
||||||
let assiduite_id = null;
|
|
||||||
|
|
||||||
createAssiduiteComplete(assiduite, etudid);
|
|
||||||
updateTableau();
|
|
||||||
btn.disabled = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function effacerFormulaire() {
|
|
||||||
const field = document.querySelector('.assi-form')
|
|
||||||
|
|
||||||
field.querySelector('#assi_date_debut').value = "";
|
|
||||||
field.querySelector('#assi_date_fin').value = "";
|
|
||||||
field.querySelector('#assi_etat').value = "attente";
|
|
||||||
field.querySelector('#raison').value = "";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function dayOnly() {
|
|
||||||
const date_deb = document.getElementById("assi_date_debut");
|
|
||||||
const date_fin = document.getElementById("assi_date_fin");
|
|
||||||
|
|
||||||
if (document.getElementById('assi_journee').checked) {
|
|
||||||
date_deb.setAttribute("show", "date")
|
|
||||||
date_fin.setAttribute("show", "date")
|
|
||||||
document.querySelector(`legend[for="assi_date_fin"]`).removeAttribute("required")
|
|
||||||
} else {
|
|
||||||
date_deb.removeAttribute("show")
|
|
||||||
date_fin.removeAttribute("show")
|
|
||||||
document.querySelector(`legend[for="assi_date_fin"]`).setAttribute("required", "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDates() {
|
|
||||||
const date_deb = document.querySelector(".page #assi_date_debut")
|
|
||||||
const date_fin = document.querySelector(".page #assi_date_fin")
|
|
||||||
const journee = document.querySelector('.page #assi_journee').checked
|
|
||||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
|
||||||
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
|
||||||
if (journee) {
|
|
||||||
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
|
||||||
} else {
|
|
||||||
fin = date_fin.valueAsObject.date + fin
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"deb": deb,
|
|
||||||
"fin": fin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const etudid = {{ sco.etud.id }};
|
|
||||||
|
|
||||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
|
||||||
const assi_morning = '{{assi_morning}}';
|
|
||||||
const assi_evening = '{{assi_evening}}';
|
|
||||||
|
|
||||||
{% if saisie_eval %}
|
|
||||||
const saisie_eval = true;
|
|
||||||
const date_deb = "{{date_deb}}";
|
|
||||||
const date_fin = "{{date_fin}}";
|
|
||||||
const moduleimpl = {{ moduleimpl_id }};
|
|
||||||
{% else %}
|
|
||||||
const saisie_eval = false;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
|
|
||||||
dayOnly()
|
|
||||||
|
|
||||||
if (saisie_eval) {
|
|
||||||
document.getElementById("assi_date_debut").value = Date.removeUTC(date_deb);
|
|
||||||
document.getElementById("assi_date_fin").value = Date.removeUTC(date_fin);
|
|
||||||
} else {
|
|
||||||
const today = (new Date()).format("YYYY-MM-DD");
|
|
||||||
document.getElementById("assi_date_debut").valueAsObject = { date: today, time: assi_morning }
|
|
||||||
document.getElementById("assi_date_fin").valueAsObject = { time: assi_evening }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.getElementById("assi_date_debut").addEventListener("blur", (event) => {
|
|
||||||
updateSelect(null, "#ajout_assiduite_module_impl", event.target.valueAsObject.date)
|
|
||||||
})
|
|
||||||
|
|
||||||
updateSelect(saisie_eval ? moduleimpl : "", "#ajout_assiduite_module_impl", document.getElementById("assi_date_debut").valueAsObject.date);
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock pageContent %}
|
|
@ -1,94 +0,0 @@
|
|||||||
{% block pageContent %}
|
|
||||||
<div class="pageContent">
|
|
||||||
<h3>Assiduites et justificatifs de <span class="rouge">{{sem}}</span> </h3>
|
|
||||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
|
||||||
|
|
||||||
<h4>Assiduité :</h4>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterAssi()"></a>
|
|
||||||
<a class="icon download" onclick="downloadAssi()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
|
||||||
<h4>Justificatifs :</h4>
|
|
||||||
<span class="iconline">
|
|
||||||
<a class="icon filter" onclick="filterJusti()"></a>
|
|
||||||
<a class="icon download" onclick="downloadJusti()"></a>
|
|
||||||
</span>
|
|
||||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
const formsemestre_id = {{ formsemestre_id }};
|
|
||||||
|
|
||||||
function getFormSemestreAssiduites(action) {
|
|
||||||
const path = getUrl() + `/api/assiduites/formsemestre/${formsemestre_id}`
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data, status) => {
|
|
||||||
if (action) {
|
|
||||||
action(data)
|
|
||||||
} else {
|
|
||||||
assiduiteCallBack(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
console.error(data, status)
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormSemestreJustificatifs(action) {
|
|
||||||
const path = getUrl() + `/api/justificatifs/formsemestre/${formsemestre_id}`
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data, status) => {
|
|
||||||
if (action) {
|
|
||||||
action(data)
|
|
||||||
} else {
|
|
||||||
justificatifCallBack(data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(data, status) => {
|
|
||||||
console.error(data, status)
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAssi(action) {
|
|
||||||
try { getFormSemestreAssiduites(action) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
function getJusti(action) {
|
|
||||||
try { getFormSemestreJustificatifs(action) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
|
||||||
|
|
||||||
filterJustificatifs = {
|
|
||||||
"columns": [
|
|
||||||
"etudid",
|
|
||||||
"entry_date",
|
|
||||||
"date_debut",
|
|
||||||
"date_fin",
|
|
||||||
"etat",
|
|
||||||
"raison",
|
|
||||||
"fichier"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filterAssiduites = {
|
|
||||||
columns: [
|
|
||||||
"etudid", "entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"
|
|
||||||
],
|
|
||||||
"filters": {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadAll();
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock pageContent %}
|
|
@ -1,160 +0,0 @@
|
|||||||
{# -*- mode: jinja-html -*- #}
|
|
||||||
{% include "assiduites/widgets/toast.j2" %}
|
|
||||||
{% include "assiduites/widgets/alert.j2" %}
|
|
||||||
{% include "assiduites/widgets/prompt.j2" %}
|
|
||||||
{% include "assiduites/widgets/conflict.j2" %}
|
|
||||||
<div id="page-assiduite-content">
|
|
||||||
{% block content %}
|
|
||||||
<h2>Signalement de l'assiduité de <span class="rouge">{{sco.etud.nomprenom}}</span></h2>
|
|
||||||
|
|
||||||
<div class="infos">
|
|
||||||
Date: <span id="datestr"></span>
|
|
||||||
<input type="text" class="datepicker" name="tl_date" id="tl_date" value="{{ date }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{timeline|safe}}
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
|
||||||
<button class="btn" onclick="fastJustify(getCurrentAssiduite(etudid))" id="justif-rapide">Justifier</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="btn_group">
|
|
||||||
<button class="btn" onclick="setTimeLineTimes({{morning}},{{afternoon}})">Journée</button>
|
|
||||||
<button class="btn" onclick="setTimeLineTimes({{morning}},{{lunch}})">Matin</button>
|
|
||||||
<button class="btn" onclick="setTimeLineTimes({{lunch}},{{afternoon}})">Après-midi</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="etud_holder">
|
|
||||||
<div id="etud_row_{{sco.etud.id}}">
|
|
||||||
<div class="index"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
{% if saisie_eval %}
|
|
||||||
<div id="saisie_eval">
|
|
||||||
<br>
|
|
||||||
<h3>
|
|
||||||
La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation. <br>
|
|
||||||
Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation
|
|
||||||
</h3>
|
|
||||||
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{{diff | safe}}
|
|
||||||
|
|
||||||
<div class="legende">
|
|
||||||
<h3>Explication de la timeline</h3>
|
|
||||||
<p>
|
|
||||||
Si la période indiquée par la timeline provoque un conflit d'assiduité pour un étudiant sa ligne deviendra
|
|
||||||
rouge.
|
|
||||||
<br>
|
|
||||||
Dans ce cas il faut résoudre manuellement le conflit : cliquez sur un des boutons d'assiduités pour ouvrir
|
|
||||||
le
|
|
||||||
résolveur de conflit.
|
|
||||||
<br>
|
|
||||||
Correspondance des couleurs :
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
{% include "assiduites/widgets/legende_couleur.j2" %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Vous pouvez justifier rapidement une assiduité en saisisant l'assiduité puis en appuyant sur "Justifier"</p>
|
|
||||||
|
|
||||||
<h3>Explication de la saisie différée</h3>
|
|
||||||
<p>Si la colonne n'est pas valide elle sera affichée en rouge, passez le curseur sur la colonne pour afficher
|
|
||||||
le message d'erreur</p>
|
|
||||||
<p>Sélectionner la date de début de la colonne mettra automatiquement la date de fin à la durée d'une séance
|
|
||||||
(préférence de département)</p>
|
|
||||||
<p>Modifier le module alors que des informations sont déjà enregistrées pour la période changera leur
|
|
||||||
module.</p>
|
|
||||||
<p>Il y a 4 boutons sur la colonne permettant d'enregistrer l'information pour tous les étudiants</p>
|
|
||||||
<p>Le dernier des boutons retire l'information présente.</p>
|
|
||||||
<p>Vous pouvez ajouter des colonnes en appuyant sur le bouton + </p>
|
|
||||||
<p>Vous pouvez supprimer une colonne en appuyant sur la croix qui se situe dans le coin haut droit de la
|
|
||||||
colonne.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Ajout d'un conteneur pour le loader -->
|
|
||||||
<div class="loader-container" id="loaderContainer">
|
|
||||||
<div class="loader"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const etudid = {{ sco.etud.id }};
|
|
||||||
const nonWorkDays = [{{ nonworkdays| safe }}];
|
|
||||||
|
|
||||||
setupDate(() => {
|
|
||||||
if (updateDate()) {
|
|
||||||
actualizeEtud(etudid);
|
|
||||||
updateSelect();
|
|
||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
|
||||||
onlyAbs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
setupTimeLine(() => {
|
|
||||||
if(document.querySelector('.etud_holder .placeholder') != null){
|
|
||||||
generateAllEtudRow();
|
|
||||||
}
|
|
||||||
updateJustifyBtn();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", () => {
|
|
||||||
updateDate();
|
|
||||||
getSingleEtud(etudid);
|
|
||||||
actualizeEtud(etudid);
|
|
||||||
updateSelect()
|
|
||||||
updateJustifyBtn();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
function setTimeLineTimes(a, b) {
|
|
||||||
setPeriodValues(a, b);
|
|
||||||
updateJustifyBtn();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.forceModule = "{{ forcer_module }}"
|
|
||||||
window.forceModule = window.forceModule == "True" ? true : false
|
|
||||||
|
|
||||||
const date_deb = "{{date_deb}}";
|
|
||||||
const date_fin = "{{date_fin}}";
|
|
||||||
|
|
||||||
{% if saisie_eval %}
|
|
||||||
createColumn(
|
|
||||||
date_deb,
|
|
||||||
date_fin,
|
|
||||||
{{ moduleimpl_id }}
|
|
||||||
);
|
|
||||||
window.location.href = "#saisie_eval"
|
|
||||||
getAndUpdateCol(1)
|
|
||||||
{% else %}
|
|
||||||
createColumn();
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.justifie {
|
|
||||||
background-color: rgb(104, 104, 252);
|
|
||||||
color: whitesmoke;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
outline: none;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
</div>
|
|
@ -1,156 +0,0 @@
|
|||||||
<div>
|
|
||||||
{% if label != false%}
|
|
||||||
<label for="moduleimpl_select">
|
|
||||||
Module
|
|
||||||
</label>
|
|
||||||
{% else %}
|
|
||||||
{% endif %}
|
|
||||||
{% if moduleid %}
|
|
||||||
<select id="{{moduleid}}" class="dynaSelect">
|
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
|
||||||
</select>
|
|
||||||
{% else %}
|
|
||||||
<select id="moduleimpl_select" class="dynaSelect">
|
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
|
||||||
</select>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div id="saved" style="display: none;">
|
|
||||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
function getEtudFormSemestres() {
|
|
||||||
let semestre = {};
|
|
||||||
sync_get(getUrl() + `/api/etudiant/etudid/${etudid}/formsemestres`, (data) => {
|
|
||||||
semestre = data;
|
|
||||||
});
|
|
||||||
return semestre;
|
|
||||||
}
|
|
||||||
|
|
||||||
function filterFormSemestres(semestres, dateIso) {
|
|
||||||
const date = new Date(Date.removeUTC(dateIso));
|
|
||||||
semestres = semestres.filter((fm) => {
|
|
||||||
return date.isBetween(new Date(Date.removeUTC(fm.date_debut_iso)), new Date(Date.removeUTC(fm.date_fin_iso)), '[]');
|
|
||||||
})
|
|
||||||
|
|
||||||
return semestres;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFormSemestreProgramme(fm_id) {
|
|
||||||
let semestre = {};
|
|
||||||
sync_get(getUrl() + `/api/formsemestre/${fm_id}/programme`, (data) => {
|
|
||||||
semestre = data;
|
|
||||||
});
|
|
||||||
return semestre;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModulesImplByFormsemestre(semestres) {
|
|
||||||
const map = new Map();
|
|
||||||
|
|
||||||
semestres.forEach((fm) => {
|
|
||||||
const array = [];
|
|
||||||
|
|
||||||
const fm_p = getFormSemestreProgramme(fm.formsemestre_id);
|
|
||||||
["ressources", "saes", "modules"].forEach((r) => {
|
|
||||||
if (r in fm_p) {
|
|
||||||
fm_p[r].forEach((o) => {
|
|
||||||
array.push(getModuleInfos(o))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
map.set(fm.titre_num, array)
|
|
||||||
})
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getModuleInfos(obj) {
|
|
||||||
return {
|
|
||||||
moduleimpl_id: obj.moduleimpl_id,
|
|
||||||
titre: obj.module.titre,
|
|
||||||
code: obj.module.code,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function populateSelect(sems, selected, query) {
|
|
||||||
const select = document.querySelector(query);
|
|
||||||
select.innerHTML = document.getElementById('saved').innerHTML
|
|
||||||
sems.forEach((mods, label) => {
|
|
||||||
const optGrp = document.createElement('optgroup');
|
|
||||||
optGrp.label = label
|
|
||||||
|
|
||||||
mods.forEach((obj) => {
|
|
||||||
const opt = document.createElement('option');
|
|
||||||
opt.value = obj.moduleimpl_id;
|
|
||||||
opt.textContent = `${obj.code} ${obj.titre}`
|
|
||||||
if (obj.moduleimpl_id == selected) {
|
|
||||||
opt.setAttribute('selected', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
optGrp.appendChild(opt);
|
|
||||||
})
|
|
||||||
select.appendChild(optGrp);
|
|
||||||
})
|
|
||||||
if (selected === "autre") {
|
|
||||||
select.querySelector('option[value="autre"]').setAttribute('selected', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSelect(moduleimpl_id, query = "#moduleimpl_select", dateIso = null) {
|
|
||||||
let sem = getEtudFormSemestres()
|
|
||||||
if (!dateIso) {
|
|
||||||
dateIso = getDate().format("YYYY-MM-DD")
|
|
||||||
}
|
|
||||||
|
|
||||||
sem = filterFormSemestres(sem, dateIso)
|
|
||||||
const mod = getModulesImplByFormsemestre(sem)
|
|
||||||
populateSelect(mod, moduleimpl_id, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSelectedSelect(moduleimpl_id, query = "#moduleimpl_select") {
|
|
||||||
const mod_id = moduleimpl_id != null ? moduleimpl_id : ""
|
|
||||||
document.querySelector(query).value = `${mod_id}`.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{% if moduleid %}
|
|
||||||
const moduleimpl_dynamic_selector_id = "{{moduleid}}"
|
|
||||||
{% else %}
|
|
||||||
const moduleimpl_dynamic_selector_id = "moduleimpl_select"
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
|
||||||
document.getElementById(moduleimpl_dynamic_selector_id).addEventListener('change', (el) => {
|
|
||||||
const assi = getCurrentAssiduite(etudid);
|
|
||||||
if (assi) {
|
|
||||||
editAssiduite(assi.assiduite_id, assi.etat, [assi]);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
const conflicts = getAssiduitesConflict(etudid);
|
|
||||||
if (conflicts.length > 0) {
|
|
||||||
updateSelectedSelect(getCurrentAssiduiteModuleImplId());
|
|
||||||
}
|
|
||||||
} catch { }
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#moduleimpl_select {
|
|
||||||
width: 125px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,10 +1,7 @@
|
|||||||
{% if scu.is_assiduites_module_forced(request.args.get('formsemestre_id', None))%}
|
{% if scu.is_assiduites_module_forced(formsemestre_id)%}
|
||||||
<option value="" disabled> Saisir Module</option>
|
<option value="" disabled> Saisir Module</option>
|
||||||
{% else %}
|
{% else %}
|
||||||
<option value=""> Non spécifié </option>
|
<option value=""> Non spécifié </option>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if moduleimpl_id == "autre" %}
|
|
||||||
<option value="autre" selected> Tout module </option>
|
<option value="autre" {{ 'selected' if moduleimpl_id == 'autre' else '' }}>Autre module (pas dans la liste) [{{moduleimpl_id}}]</option>
|
||||||
{% else %}
|
|
||||||
<option value="autre"> Tout module </option>
|
|
||||||
{% endif %}
|
|
||||||
|
@ -1,465 +0,0 @@
|
|||||||
<table id="assiduiteTable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Début</span>
|
|
||||||
<a class="icon order" onclick="order('date_debut', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Fin</span>
|
|
||||||
<a class="icon order" onclick="order('date_fin', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>État</span>
|
|
||||||
<a class="icon order" onclick="order('etat', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Module</span>
|
|
||||||
<a class="icon order" onclick="order('moduleimpl_id', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
<div>
|
|
||||||
<span>Justifiée</span>
|
|
||||||
<a class="icon order" onclick="order('est_just', assiduiteCallBack, this)"></a>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="tableBodyAssiduites">
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<div id="paginationContainerAssiduites" class="pagination-container">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display: none;" id="cache-module">
|
|
||||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const paginationContainerAssiduites = document.getElementById("paginationContainerAssiduites");
|
|
||||||
let currentPageAssiduites = 1;
|
|
||||||
let orderAssiduites = true;
|
|
||||||
let filterAssiduites = {
|
|
||||||
columns: [
|
|
||||||
"entry_date", "date_debut", "date_fin", "etat", "moduleimpl_id", "est_just"
|
|
||||||
],
|
|
||||||
filters: {}
|
|
||||||
}
|
|
||||||
const tableBodyAssiduites = document.getElementById("tableBodyAssiduites");
|
|
||||||
|
|
||||||
function assiduiteCallBack(assi) {
|
|
||||||
assi = filterArray(assi, filterAssiduites.filters)
|
|
||||||
renderTableAssiduites(currentPageAssiduites, assi);
|
|
||||||
renderPaginationButtons(assi);
|
|
||||||
|
|
||||||
try { stats() } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function renderTableAssiduites(page, assiduités) {
|
|
||||||
|
|
||||||
generateTableHead(filterAssiduites.columns, true)
|
|
||||||
|
|
||||||
tableBodyAssiduites.innerHTML = "";
|
|
||||||
const start = (page - 1) * itemsPerPage;
|
|
||||||
const end = start + itemsPerPage;
|
|
||||||
|
|
||||||
assiduités.slice(start, end).forEach((assiduite) => {
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
row.setAttribute('type', "assiduite");
|
|
||||||
row.setAttribute('obj_id', assiduite.assiduite_id);
|
|
||||||
|
|
||||||
const etat = assiduite.etat.toLowerCase();
|
|
||||||
row.classList.add(`l-${etat}`);
|
|
||||||
filterAssiduites.columns.forEach((k) => {
|
|
||||||
const td = document.createElement('td');
|
|
||||||
if (k.indexOf('date') != -1) {
|
|
||||||
td.textContent = new Date(Date.removeUTC(assiduite[k])).format(`DD/MM/Y HH:mm`)
|
|
||||||
} else if (k.indexOf("module") != -1) {
|
|
||||||
td.textContent = getModuleImpl(assiduite);
|
|
||||||
} else if (k.indexOf('est_just') != -1) {
|
|
||||||
td.textContent = assiduite[k] ? "Oui" : "Non"
|
|
||||||
if (assiduite[k]) row.classList.add("est_just")
|
|
||||||
} else if (k.indexOf('etudid') != -1) {
|
|
||||||
const e = getEtudiant(assiduite.etudid);
|
|
||||||
|
|
||||||
td.innerHTML = `<a class="etudinfo" id="line-${assiduite.etudid}" href="bilan_etud?etudid=${assiduite.etudid}">${e.prenom.capitalize()} ${e.nom.toUpperCase()}</a>`;
|
|
||||||
} else {
|
|
||||||
td.textContent = assiduite[k].capitalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
row.appendChild(td)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
row.addEventListener("contextmenu", openContext);
|
|
||||||
|
|
||||||
tableBodyAssiduites.appendChild(row);
|
|
||||||
});
|
|
||||||
updateActivePaginationButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
function detailAssiduites(assiduite_id) {
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite_id}`;
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data) => {
|
|
||||||
const user = getUser(data);
|
|
||||||
const module = getModuleImpl(data);
|
|
||||||
|
|
||||||
const date_debut = new Date(Date.removeUTC(data.date_debut)).format("DD/MM/YYYY HH:mm");
|
|
||||||
const date_fin = new Date(Date.removeUTC(data.date_fin)).format("DD/MM/YYYY HH:mm");
|
|
||||||
const entry_date = new Date(Date.removeUTC(data.entry_date)).format("DD/MM/YYYY HH:mm");
|
|
||||||
|
|
||||||
const etat = data.etat.capitalize();
|
|
||||||
const desc = data.desc == null ? "" : data.desc;
|
|
||||||
const id = data.assiduite_id;
|
|
||||||
const est_just = data.est_just ? "Oui" : "Non";
|
|
||||||
|
|
||||||
const html = `
|
|
||||||
<div class="obj-detail">
|
|
||||||
<div class="obj-dates">
|
|
||||||
<div id="date_debut" class="obj-part">
|
|
||||||
<span class="obj-title">Date de début</span>
|
|
||||||
<span class="obj-content">${date_debut}</span>
|
|
||||||
</div>
|
|
||||||
<div id="date_fin" class="obj-part">
|
|
||||||
<span class="obj-title">Date de fin</span>
|
|
||||||
<span class="obj-content">${date_fin}</span>
|
|
||||||
</div>
|
|
||||||
<div id="entry_date" class="obj-part">
|
|
||||||
<span class="obj-title">Date de saisie</span>
|
|
||||||
<span class="obj-content">${entry_date}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="obj-mod">
|
|
||||||
<div id="module" class="obj-part">
|
|
||||||
<span class="obj-title">Module</span>
|
|
||||||
<span class="obj-content">${module}</span>
|
|
||||||
</div>
|
|
||||||
<div id="etat" class="obj-part">
|
|
||||||
<span class="obj-title">Etat</span>
|
|
||||||
<span class="obj-content">${etat}</span>
|
|
||||||
</div>
|
|
||||||
<div id="user" class="obj-part">
|
|
||||||
<span class="obj-title">par</span>
|
|
||||||
<span class="obj-content">${user}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="obj-rest">
|
|
||||||
<div id="est_just" class="obj-part">
|
|
||||||
<span class="obj-title">Justifié</span>
|
|
||||||
<span class="obj-content">${est_just}</span>
|
|
||||||
</div>
|
|
||||||
<div id="desc" class="obj-part">
|
|
||||||
<span class="obj-title">Description</span>
|
|
||||||
<p class="obj-content">${desc}</p>
|
|
||||||
</div>
|
|
||||||
<div id="id" class="obj-part" data-assiduite-id="${id}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const el = document.createElement('div');
|
|
||||||
el.innerHTML = html;
|
|
||||||
|
|
||||||
openAlertModal("Détails", el.firstElementChild, null, "var(--color-information)")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function editionAssiduites(assiduite_id) {
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite_id}`;
|
|
||||||
async_get(
|
|
||||||
path,
|
|
||||||
(data) => {
|
|
||||||
let module = data.moduleimpl_id;
|
|
||||||
if (
|
|
||||||
module == null && data.hasOwnProperty("external_data") &&
|
|
||||||
data.external_data != null &&
|
|
||||||
data.external_data.hasOwnProperty('module')
|
|
||||||
) {
|
|
||||||
module = data.external_data.module.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
const etat = data.etat;
|
|
||||||
let desc = data.desc == null ? "" : data.desc;
|
|
||||||
const html = `
|
|
||||||
<div class="assi-edit">
|
|
||||||
<div class="assi-edit-part">
|
|
||||||
<legend>État de l'assiduité</legend>
|
|
||||||
<select name="etat" id="etat">
|
|
||||||
<option value="present">Présent</option>
|
|
||||||
<option value="retard">En Retard</option>
|
|
||||||
<option value="absent">Absent</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="assi-edit-part">
|
|
||||||
<legend>Module</legend>
|
|
||||||
<select name="module" id="module">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="assi-edit-part">
|
|
||||||
<legend>Description</legend>
|
|
||||||
<textarea name="desc" id="desc" cols="50" rows="10" maxlength="500"></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const el = document.createElement('div')
|
|
||||||
el.innerHTML = html;
|
|
||||||
const assiEdit = el.firstElementChild;
|
|
||||||
|
|
||||||
assiEdit.querySelector('#etat').value = etat.toLowerCase();
|
|
||||||
assiEdit.querySelector('#desc').value = desc != null ? desc : "";
|
|
||||||
updateSelect(module, '#moduleimpl_select', data.date_debut.split('T')[0])
|
|
||||||
assiEdit.querySelector('#module').replaceWith(document.querySelector('#moduleimpl_select').cloneNode(true));
|
|
||||||
openPromptModal("Modification de l'assiduité", assiEdit, () => {
|
|
||||||
const prompt = document.querySelector('.assi-edit');
|
|
||||||
const etat = prompt.querySelector('#etat').value;
|
|
||||||
const desc = prompt.querySelector('#desc').value;
|
|
||||||
let module = prompt.querySelector('#moduleimpl_select').value;
|
|
||||||
let edit = {
|
|
||||||
"etat": etat,
|
|
||||||
"desc": desc,
|
|
||||||
"external_data": data.external_data
|
|
||||||
}
|
|
||||||
|
|
||||||
edit = setModuleImplId(edit, module);
|
|
||||||
|
|
||||||
fullEditAssiduites(data.assiduite_id, edit, () => {
|
|
||||||
loadAll();
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-information)");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fullEditAssiduites(assiduite_id, obj, call = () => { }) {
|
|
||||||
const path = getUrl() + `/api/assiduite/${assiduite_id}/edit`;
|
|
||||||
async_post(
|
|
||||||
path,
|
|
||||||
obj,
|
|
||||||
call,
|
|
||||||
(data, status) => {
|
|
||||||
//error
|
|
||||||
console.error(data, status);
|
|
||||||
errorAlert();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
<span class="filter-line">
|
|
||||||
<span class="filter-title" for="etud">Rechercher dans les étudiants</span>
|
|
||||||
<input type="text" name="etud" id="etud" placeholder="Anne Onymous" >
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.innerHTML = html
|
|
||||||
html = span.firstElementChild
|
|
||||||
|
|
||||||
const filterHead = html.querySelector('.filter-head');
|
|
||||||
filterHead.innerHTML = ""
|
|
||||||
let cols = ["etudid", "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];
|
|
||||||
} else if (key == "etud") {
|
|
||||||
l.querySelector('#etud').value = filterAssiduites.filters["etud"];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 Date(Date.removeUTC(time))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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;
|
|
||||||
} else if (key == "etud") {
|
|
||||||
filterAssiduites.filters["etud"] = l.querySelector('#etud').value;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
getAssi(assiduiteCallBack)
|
|
||||||
|
|
||||||
}, () => { }, "var(--color-primary)");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAssi() {
|
|
||||||
getAssi((d) => { toCSV(d, filterAssiduites) })
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAssi(action) {
|
|
||||||
try { getAllAssiduitesFromEtud(etudid, action, true, true, assi_limit_annee) } catch (_) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
@ -2101,15 +2101,19 @@ def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> s
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/widgets/moduleimpl_selector.j2",
|
"assiduites/widgets/moduleimpl_selector.j2",
|
||||||
selected=selected,
|
formsemestre_id=formsemestre.id,
|
||||||
modules=modules,
|
modules=modules,
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
|
selected=selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _module_selector_multiple(
|
def _module_selector_multiple(
|
||||||
etud: Identite, moduleimpl_id: int = None, only_form: FormSemestre = None
|
etud: Identite, moduleimpl_id: int = None, only_form: FormSemestre = None
|
||||||
) -> str:
|
) -> str:
|
||||||
|
"""menu HTML <select> pour choix moduleimpl
|
||||||
|
Prend les semestres de l'année, sauf si only_form est indiqué.
|
||||||
|
"""
|
||||||
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
||||||
choices = {}
|
choices = {}
|
||||||
for formsemestre_id in modimpls_by_formsemestre:
|
for formsemestre_id in modimpls_by_formsemestre:
|
||||||
@ -2129,22 +2133,13 @@ def _module_selector_multiple(
|
|||||||
return render_template(
|
return render_template(
|
||||||
"assiduites/widgets/moduleimpl_selector_multiple.j2",
|
"assiduites/widgets/moduleimpl_selector_multiple.j2",
|
||||||
choices=choices,
|
choices=choices,
|
||||||
|
formsemestre_id=only_form.id
|
||||||
|
if only_form
|
||||||
|
else list(modimpls_by_formsemestre.keys())[0],
|
||||||
moduleimpl_id=moduleimpl_id,
|
moduleimpl_id=moduleimpl_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _dynamic_module_selector() -> str:
|
|
||||||
"""
|
|
||||||
_dynamic_module_selector retourne l'html/css/javascript du selecteur de module dynamique
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: l'html/css/javascript du selecteur de module dynamique
|
|
||||||
"""
|
|
||||||
return render_template(
|
|
||||||
"assiduites/widgets/moduleimpl_dynamic_selector.j2",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _timeline(formsemestre_id: int = None, heures=None) -> str:
|
def _timeline(formsemestre_id: int = None, heures=None) -> str:
|
||||||
"""
|
"""
|
||||||
_timeline retourne l'html de la timeline
|
_timeline retourne l'html de la timeline
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- mode: python -*-
|
# -*- mode: python -*-
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
SCOVERSION = "9.6.82"
|
SCOVERSION = "9.6.83"
|
||||||
|
|
||||||
SCONAME = "ScoDoc"
|
SCONAME = "ScoDoc"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user