Merge branch 'main96' of https://scodoc.org/git/iziram/ScoDoc into iziram-main96
This commit is contained in:
commit
b91609950c
@ -151,7 +151,9 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
|
||||
@as_json
|
||||
@permission_required(Permission.ScoView)
|
||||
def justificatifs_dept(dept_id: int = None, with_query: bool = False):
|
||||
"""XXX TODO missing doc"""
|
||||
"""
|
||||
Renvoie tous les justificatifs d'un département (en ajoutant un champs "formsemestre" si possible)
|
||||
"""
|
||||
|
||||
# Récupération du département et des étudiants du département
|
||||
dept: Departement = Departement.query.get(dept_id)
|
||||
|
@ -109,7 +109,6 @@ def do_abs_notify(
|
||||
return # abort
|
||||
|
||||
# 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")
|
||||
destinations_filtered = []
|
||||
for email_addr in destinations:
|
||||
|
@ -763,5 +763,7 @@ def simple_invalidate_cache(obj: dict, etudid: str | int = None):
|
||||
|
||||
# Invalide les caches des tableaux de l'étudiant
|
||||
sco_cache.RequeteTableauAssiduiteCache.delete_pattern(
|
||||
pattern=f"tableau-etud-{etudid}:*"
|
||||
pattern=f"tableau-etud-{etudid}*"
|
||||
)
|
||||
# Invalide les tableaux "bilan dept"
|
||||
sco_cache.RequeteTableauAssiduiteCache.delete_pattern(pattern=f"tableau-dept*")
|
||||
|
@ -400,7 +400,7 @@ class ValidationsSemestreCache(ScoDocCache):
|
||||
|
||||
class RequeteTableauAssiduiteCache(ScoDocCache):
|
||||
"""
|
||||
clé : "<titre_tableau>:<type_obj>:<show_pres>:<show_retard>>:<order_col>:<order>"
|
||||
clé : "<titre_tableau>:<type_obj>:<show_pres>:<show_retard>:<show_desc>:<order_col>:<order>"
|
||||
Valeur = liste de dicts
|
||||
"""
|
||||
|
||||
|
@ -919,7 +919,7 @@ def _make_listes_sem(formsemestre: FormSemestre) -> str:
|
||||
<a class="btn" href="{
|
||||
url_for("assiduites.bilan_dept",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre=formsemestre.id,
|
||||
formsemestre_id=formsemestre.id,
|
||||
group_ids=group.id,
|
||||
)}">
|
||||
<button>Justificatifs en attente</button></a>
|
||||
|
@ -541,18 +541,6 @@ class BasePreferences:
|
||||
"category": "abs",
|
||||
},
|
||||
),
|
||||
(
|
||||
"abs_notify_max_freq",
|
||||
{
|
||||
"initvalue": 7,
|
||||
"title": "Fréquence maximale de notification",
|
||||
"explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ",
|
||||
"size": 4,
|
||||
"type": "int",
|
||||
"convert_numbers": True,
|
||||
"category": "abs",
|
||||
},
|
||||
),
|
||||
(
|
||||
"abs_notify_abs_threshold",
|
||||
{
|
||||
@ -710,11 +698,23 @@ class BasePreferences:
|
||||
"size": 10,
|
||||
"title": "Seuil d'alerte des absences",
|
||||
"type": "int",
|
||||
"explanation": "Nombres d'absences limite avant alerte dans le bilan (utilisation de l'unité métrique ↑ )",
|
||||
"explanation": "Nombres d'absences limite avant alerte (utilisation de l'unité métrique ↑ )",
|
||||
"category": "assi",
|
||||
"only_global": True,
|
||||
},
|
||||
),
|
||||
(
|
||||
"abs_notify_max_freq",
|
||||
{
|
||||
"initvalue": 7,
|
||||
"title": "Fréquence maximale de notification",
|
||||
"explanation": "nb de jours minimum entre deux mails envoyés au même destinataire à propos d'un même étudiant ",
|
||||
"size": 4,
|
||||
"type": "int",
|
||||
"convert_numbers": True,
|
||||
"category": "abs",
|
||||
},
|
||||
),
|
||||
# portal
|
||||
(
|
||||
"portal_url",
|
||||
|
@ -298,15 +298,11 @@ def is_iso_formated(date: str, convert=False) -> bool or datetime.datetime or No
|
||||
return None if convert else False
|
||||
|
||||
|
||||
def localize_datetime(date: datetime.datetime or str) -> datetime.datetime:
|
||||
def localize_datetime(date: datetime.datetime) -> datetime.datetime:
|
||||
"""Transforme une date sans offset en une date avec offset
|
||||
Tente de mettre l'offset de la timezone du serveur (ex : UTC+1)
|
||||
Si erreur, mettra l'offset UTC
|
||||
|
||||
TODO : vérifier puis supprimer l'auto conversion str-> datetime
|
||||
"""
|
||||
if isinstance(date, str):
|
||||
date = is_iso_formated(date, convert=True)
|
||||
|
||||
new_date: datetime.datetime = date
|
||||
if new_date.tzinfo is None:
|
||||
|
@ -1,3 +1,4 @@
|
||||
// TODO : Supprimer les fonctions non utilisées + optimiser les fonctions utilisées
|
||||
// <=== CONSTANTS and GLOBALS ===>
|
||||
|
||||
let url;
|
||||
|
@ -140,6 +140,7 @@ class ListeAssiJusti(tb.Table):
|
||||
type_obj,
|
||||
self.options.show_pres,
|
||||
self.options.show_reta,
|
||||
self.options.show_desc,
|
||||
self.options.order[0],
|
||||
self.options.order[1],
|
||||
],
|
||||
@ -152,12 +153,18 @@ class ListeAssiJusti(tb.Table):
|
||||
assiduites_query_etudiants = self.table_data.assiduites_query
|
||||
|
||||
# Non affichage des présences
|
||||
if not self.options.show_pres:
|
||||
if (
|
||||
not self.options.show_pres
|
||||
and assiduites_query_etudiants is not None
|
||||
):
|
||||
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
||||
Assiduite.etat != EtatAssiduite.PRESENT
|
||||
)
|
||||
# Non affichage des retards
|
||||
if not self.options.show_reta:
|
||||
if (
|
||||
not self.options.show_reta
|
||||
and assiduites_query_etudiants is not None
|
||||
):
|
||||
assiduites_query_etudiants = assiduites_query_etudiants.filter(
|
||||
Assiduite.etat != EtatAssiduite.RETARD
|
||||
)
|
||||
@ -266,7 +273,7 @@ class ListeAssiJusti(tb.Table):
|
||||
]
|
||||
|
||||
if self.options.show_desc:
|
||||
assiduites_entities.append(Assiduite.description.label("description"))
|
||||
assiduites_entities.append(Assiduite.description.label("desc"))
|
||||
|
||||
query_assiduite = query_assiduite.with_entities(*assiduites_entities)
|
||||
queries.append(query_assiduite)
|
||||
@ -288,7 +295,7 @@ class ListeAssiJusti(tb.Table):
|
||||
]
|
||||
|
||||
if self.options.show_desc:
|
||||
justificatifs_entities.append(Justificatif.raison.label("description"))
|
||||
justificatifs_entities.append(Justificatif.raison.label("desc"))
|
||||
|
||||
query_justificatif = query_justificatif.with_entities(
|
||||
*justificatifs_entities
|
||||
@ -466,7 +473,7 @@ class RowAssiJusti(tb.Row):
|
||||
self.add_cell(
|
||||
"description",
|
||||
"Description",
|
||||
self.ligne["description"] if self.ligne["description"] else "",
|
||||
self.ligne["desc"] if self.ligne["desc"] else "",
|
||||
)
|
||||
if self.table.options.show_module:
|
||||
if self.ligne["type"] == "assiduite":
|
||||
|
@ -1,187 +1,27 @@
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<section class="alerte invisible">
|
||||
<p>Attention, cet étudiant a trop d'absences</p>
|
||||
</section>
|
||||
{% extends "sco_page.j2" %}
|
||||
{% block styles %}
|
||||
{{super()}}
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||
{% endblock styles %}
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||
{% endblock scripts %}
|
||||
{% block app_content %}
|
||||
<h2>Traitement de l'assiduité</h2>
|
||||
<p class="help">
|
||||
Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par
|
||||
le semestre concerné (saisie par jour ou saisie différée).
|
||||
</p>
|
||||
<p class="help">Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant,
|
||||
choisissez d'abord la personne concernée :</p>
|
||||
<br>
|
||||
{{search_etud | safe}}
|
||||
<br>
|
||||
{{billets | safe}}
|
||||
|
||||
<br>
|
||||
<section class="nonvalide">
|
||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||
<h4>Justificatifs en attente (ou modifiés)</h4>
|
||||
<span class="iconline">
|
||||
<a class="icon filter" onclick="filterJusti(true)"></a>
|
||||
<a class="icon download" onclick="downloadJusti()"></a>
|
||||
</span>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
|
||||
<div class="annee">
|
||||
<span>Année scolaire 2022-2023 Changer année: </span>
|
||||
<select name="" id="annee" onchange="setterAnnee(this.value)">
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="legende">
|
||||
<h3>Gestion des justificatifs</h3>
|
||||
<p>
|
||||
Faites
|
||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu contextuel :
|
||||
<ul>
|
||||
<li>Détails : Affiche les détails du justificatif sélectionné</li>
|
||||
<li>Editer : Permet de modifier le justificatif (dates, etat, ajouter/supprimer fichier etc)</li>
|
||||
<li>Supprimer : Permet de supprimer le justificatif (Action Irréversible)</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<script>
|
||||
|
||||
let formsemestre_id = "{{formsemestre_id}}"
|
||||
let group_id = "{{group_id}}"
|
||||
|
||||
function getDeptJustificatifsFromPeriod(action) {
|
||||
const formsemestre = formsemestre_id ? `&formsemestre_id=${formsemestre_id}` : ""
|
||||
const group = group_id ? `&group_id=${group_id}` : ""
|
||||
const path = getUrl() + `/api/justificatifs/dept/${dept_id}/query?date_debut=${bornes.deb}&date_fin=${bornes.fin}${formsemestre}${group}`
|
||||
async_get(
|
||||
path,
|
||||
(data, status) => {
|
||||
if (action) {
|
||||
action(data)
|
||||
} else {
|
||||
justificatifCallBack(data);
|
||||
}
|
||||
},
|
||||
(data, status) => {
|
||||
console.error(data, status)
|
||||
errorAlert();
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function generate(annee) {
|
||||
|
||||
if (annee < 1999 || annee > 2999) {
|
||||
openAlertModal("Année impossible", document.createTextNode("L'année demandé n'existe pas."));
|
||||
return;
|
||||
}
|
||||
bornes = {
|
||||
deb: `${annee}-09-01T00:00`,
|
||||
fin: `${annee + 1}-08-31T23:59`
|
||||
}
|
||||
|
||||
defAnnee = annee;
|
||||
|
||||
loadAll();
|
||||
|
||||
}
|
||||
|
||||
function getJusti(action) {
|
||||
try { getDeptJustificatifsFromPeriod(action) } catch (_) { }
|
||||
}
|
||||
|
||||
function setterAnnee(annee) {
|
||||
annee = parseInt(annee);
|
||||
document.querySelector('.annee span').textContent = `Année scolaire ${annee}-${annee + 1} Changer année: `
|
||||
generate(annee)
|
||||
|
||||
}
|
||||
let defAnnee = {{ annee }};
|
||||
let bornes = {
|
||||
deb: `${defAnnee}-09-01T00:00`,
|
||||
fin: `${defAnnee + 1}-08-31T23:59`
|
||||
}
|
||||
const dept_id = {{ dept_id }};
|
||||
|
||||
let annees = {{ annees | safe}}
|
||||
|
||||
annees = annees.filter((x, i) => annees.indexOf(x) === i)
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
|
||||
filterJustificatifs = {
|
||||
"columns": [
|
||||
"formsemestre",
|
||||
"etudid",
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"etat",
|
||||
"raison",
|
||||
"fichier"
|
||||
],
|
||||
"filters": {
|
||||
"etat": [
|
||||
"attente",
|
||||
"modifie"
|
||||
],
|
||||
}
|
||||
}
|
||||
const select = document.querySelector('#annee');
|
||||
|
||||
annees.forEach((a) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = a + "",
|
||||
opt.textContent = `${a} - ${a + 1}`;
|
||||
if (a === defAnnee) {
|
||||
opt.selected = true;
|
||||
}
|
||||
select.appendChild(opt)
|
||||
})
|
||||
setterAnnee(defAnnee)
|
||||
})
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.stats-values-item {
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stats {
|
||||
border: 1px solid #333;
|
||||
padding: 5px 2px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.stats-values {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.stats-values-item h5 {
|
||||
font-weight: bold;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.stats-values-part {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerte {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border-radius: 7px;
|
||||
|
||||
background-color: var(--color-error);
|
||||
|
||||
}
|
||||
|
||||
.alerte.invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alerte p {
|
||||
font-size: larger;
|
||||
color: whitesmoke;
|
||||
|
||||
}
|
||||
|
||||
.suppr {
|
||||
margin: 5px 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock app_content %}
|
@ -25,27 +25,7 @@
|
||||
</section>
|
||||
|
||||
<section class="nonvalide">
|
||||
<!-- Tableaux des assiduités (retard/abs) non justifiées -->
|
||||
<h4>Absences et retards non justifiés</h4>
|
||||
|
||||
{# TODO Utiliser python tableau plutot que js tableau #}
|
||||
<div class="ue_warning">Attention, cette page utilise des couleurs et conventions différentes
|
||||
de celles des autres pages ScoDoc: elle sera prochainement modifée, merci de votre patience.
|
||||
</div>
|
||||
|
||||
<span class="iconline">
|
||||
<a class="icon filter" onclick="filterAssi()"></a>
|
||||
<a class="icon download" onclick="downloadAssi()"></a>
|
||||
</span>
|
||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||
<!-- Tableaux des justificatifs à valider (attente / modifié ) -->
|
||||
<h4>Justificatifs en attente (ou modifiés)</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" %}
|
||||
|
||||
{{tableau | safe }}
|
||||
</section>
|
||||
|
||||
<section class="suppr">
|
||||
@ -60,29 +40,6 @@
|
||||
département)</p>
|
||||
<p>Les statistiques sont calculées entre les deux dates sélectionnées. Après modification des dates,
|
||||
appuyer sur le bouton "Actualiser"</p>
|
||||
<h3>Gestion des justificatifs</h3>
|
||||
<p>
|
||||
Faites
|
||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
||||
contextuel :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Détails : affiche les détails du justificatif sélectionné</li>
|
||||
<li>Éditer : modifie le justificatif (dates, état, ajouter/supprimer fichier, etc.)</li>
|
||||
<li>Supprimer : supprime le justificatif (action irréversible)</li>
|
||||
</ul>
|
||||
|
||||
<h3>Gestion de l'assiduité</h3>
|
||||
<p>
|
||||
Faites
|
||||
<span style="font-style: italic;">clic droit</span> sur une ligne du tableau pour afficher le menu
|
||||
contextuel :
|
||||
</p>
|
||||
<ul>
|
||||
<li>Détails : affiche les détails de l'élément sélectionnée</li>
|
||||
<li>Editer : modifie l'élément (module, état)</li>
|
||||
<li>Supprimer : supprime l'élément (action irréversible)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@ -275,48 +232,8 @@
|
||||
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
filterAssiduites = {
|
||||
"columns": [
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"etat",
|
||||
"moduleimpl_id",
|
||||
"est_just"
|
||||
],
|
||||
"filters": {
|
||||
"etat": [
|
||||
"retard",
|
||||
"absent"
|
||||
],
|
||||
"moduleimpl_id": "",
|
||||
"est_just": "false"
|
||||
}
|
||||
};
|
||||
|
||||
filterJustificatifs = {
|
||||
"columns": [
|
||||
"entry_date",
|
||||
"date_debut",
|
||||
"date_fin",
|
||||
"etat",
|
||||
"raison",
|
||||
"fichier"
|
||||
],
|
||||
"filters": {
|
||||
"etat": [
|
||||
"attente",
|
||||
"modifie"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('stats_date_fin').value = assi_date_fin;
|
||||
document.getElementById('stats_date_debut').value = assi_date_debut;
|
||||
|
||||
|
||||
|
||||
loadAll();
|
||||
stats();
|
||||
})
|
||||
|
||||
|
@ -1,5 +1,14 @@
|
||||
{#
|
||||
|
||||
- TODO : revoir le fonctionnement de cette page (trop lente / complexe)
|
||||
- Utiliser majoritairement du python
|
||||
#}
|
||||
<h2>Signalement différé de l'assiduité {{gr |safe}}</h2>
|
||||
|
||||
<div class="ue_warning">Attention, cette page utilise des couleurs et conventions différentes
|
||||
de celles des autres pages ScoDoc: elle sera prochainement modifée, merci de votre patience.
|
||||
</div>
|
||||
|
||||
<h3>{{sem | safe }}</h3>
|
||||
|
||||
{{diff | safe}}
|
||||
|
@ -3,5 +3,6 @@
|
||||
<div class="assiduite-period">{{date_debut}}</div>
|
||||
<div class="assiduite-period">{{date_fin}}</div>
|
||||
<div class="assiduite-state">État: {{etat}}</div>
|
||||
<div class="assiduite-why">Motif: {{motif}}</div>
|
||||
<div class="assiduite-user_id">{{saisie}}</div>
|
||||
</div>
|
@ -77,12 +77,6 @@
|
||||
const duration = (endTime - startTime) / 1000 / 60;
|
||||
|
||||
const percent = (duration / (t_end * 60 - t_start * 60)) * 100
|
||||
|
||||
if (percent > 100) {
|
||||
console.log(start, end);
|
||||
console.log(startTime, endTime)
|
||||
}
|
||||
|
||||
return percent + "%";
|
||||
}
|
||||
|
||||
@ -253,13 +247,11 @@
|
||||
*/
|
||||
splitAssiduiteModal() {
|
||||
//Préparation du prompt
|
||||
// TODO utiliser timepicker jquery + utiliser les bornes (t_start et t_end)
|
||||
const htmlPrompt = `<legend>Entrez l'heure de séparation (HH:mm) :</legend>
|
||||
<input type="time" id="promptTime" name="appt"
|
||||
min="08:00" max="18:00" required>`;
|
||||
const htmlPrompt = `<legend>Entrez l'heure de séparation</legend>
|
||||
<input type="text" id="promptTime" name="appt"required style="position: relative; z-index: 100000;">`;
|
||||
|
||||
const fieldSet = document.createElement("fieldset");
|
||||
fieldSet.classList.add("fieldsplit");
|
||||
fieldSet.classList.add("fieldsplit", "timepicker");
|
||||
fieldSet.innerHTML = htmlPrompt;
|
||||
|
||||
//Callback de division
|
||||
@ -317,11 +309,28 @@
|
||||
"L'heure de séparation doit être compris dans la période de l'assiduité sélectionnée."
|
||||
);
|
||||
|
||||
openAlertModal("Attention", att, "", "var(--color-warning))");
|
||||
openAlertModal("Attention", att, "", "var(--color-warning)");
|
||||
}
|
||||
};
|
||||
|
||||
openPromptModal("Séparation de l'assiduité sélectionnée", fieldSet, success, () => { }, "var(--color-present)");
|
||||
// Initialisation du timepicker
|
||||
const deb = this.selectedAssiduite.date_debut.substring(11,16);
|
||||
const fin = this.selectedAssiduite.date_fin.substring(11,16);
|
||||
setTimeout(()=>{
|
||||
$('#promptTime').timepicker({
|
||||
timeFormat: 'HH:mm',
|
||||
interval: 60 * tick_delay,
|
||||
minTime: deb,
|
||||
startTime: deb,
|
||||
maxTime: fin,
|
||||
dynamic: false,
|
||||
dropdown: true,
|
||||
scrollbar: false,
|
||||
|
||||
});
|
||||
}, 100
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -466,4 +475,9 @@
|
||||
this.editBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
<style>
|
||||
.ui-timepicker-container {
|
||||
z-index: 100000 !important;
|
||||
}
|
||||
</style>
|
@ -157,6 +157,11 @@
|
||||
stateDiv.textContent = `État: ${assiduite.etat.capitalize()}`;
|
||||
bubble.appendChild(stateDiv);
|
||||
|
||||
const motifDiv = document.createElement("div");
|
||||
stateDiv.className = "assiduite-why";
|
||||
stateDiv.textContent = `Motif: ${assiduite.desc?.capitalize()}`;
|
||||
bubble.appendChild(motifDiv);
|
||||
|
||||
const userIdDiv = document.createElement("div");
|
||||
userIdDiv.className = "assiduite-user_id";
|
||||
userIdDiv.textContent = `saisie le ${formatDateModal(
|
||||
|
@ -0,0 +1,20 @@
|
||||
<select name="moduleimpl_select" id="moduleimpl_select">
|
||||
|
||||
{% with moduleimpl_id=moduleimpl_id %}
|
||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||
{% endwith %}
|
||||
|
||||
{% for cat, mods in choices.items() %}
|
||||
<optgroup label="{{cat}}">
|
||||
{% for mod in mods %}
|
||||
{% if mod.moduleimpl_id == moduleimpl_id %}
|
||||
<option value="{{mod.moduleimpl_id}}" selected> {{mod.name}} </option>
|
||||
{% else %}
|
||||
<option value="{{mod.moduleimpl_id}}"> {{mod.name}} </option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% endfor %}
|
||||
|
||||
|
||||
</select>
|
@ -178,62 +178,25 @@ class HTMLBuilder:
|
||||
def bilan_dept():
|
||||
"""Gestionnaire assiduités, page principale"""
|
||||
|
||||
# Préparation de la page
|
||||
H = [
|
||||
html_sco_header.sco_header(
|
||||
page_title="Saisie de l'assiduité",
|
||||
javascripts=[
|
||||
"js/assiduites.js",
|
||||
"js/date_utils.js",
|
||||
],
|
||||
cssstyles=[
|
||||
"css/assiduites.css",
|
||||
],
|
||||
),
|
||||
"""<h2>Traitement de l'assiduité</h2>
|
||||
<p class="help">
|
||||
Pour saisir l'assiduité ou consulter les états, il est recommandé de passer par
|
||||
le semestre concerné (saisie par jour ou saisie différée).
|
||||
</p>
|
||||
""",
|
||||
]
|
||||
H.append(
|
||||
"""<p class="help">Pour signaler, annuler ou justifier l'assiduité d'un seul étudiant,
|
||||
choisissez d'abord la personne concernée :</p>"""
|
||||
)
|
||||
# Ajout de la barre de recherche d'étudiant (redirection vers bilan etud)
|
||||
H.append(sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"))
|
||||
|
||||
# Gestion des billets d'absences
|
||||
if current_user.has_permission(
|
||||
Permission.AbsChange
|
||||
) and sco_preferences.get_preference("handle_billets_abs"):
|
||||
H.append(
|
||||
f"""
|
||||
billets = f"""
|
||||
<h2 style="margin-top: 30px;">Billets d'absence</h2>
|
||||
<ul><li><a href="{url_for("absences.list_billets", scodoc_dept=g.scodoc_dept)
|
||||
}">Traitement des billets d'absence en attente</a>
|
||||
</li></ul>
|
||||
"""
|
||||
)
|
||||
|
||||
# Récupération des années d'étude du département
|
||||
# (afin de sélectionner une année)
|
||||
else:
|
||||
billets = ""
|
||||
# Récupération du département
|
||||
dept: Departement = Departement.query.filter_by(id=g.scodoc_dept_id).first()
|
||||
annees: list[int] = sorted(
|
||||
[f.date_debut.year for f in dept.formsemestres],
|
||||
reverse=True,
|
||||
)
|
||||
annee = scu.annee_scolaire() # Année courante, sera utilisée par défaut
|
||||
# Génération d'une liste "json" d'années
|
||||
annees_str: str = "["
|
||||
for ann in annees:
|
||||
annees_str += f"{ann},"
|
||||
annees_str += "]"
|
||||
|
||||
# Récupération d'un formsemestre
|
||||
# (pour n'afficher que les assiduites/justificatifs liés au formsemestre)
|
||||
# (pour n'afficher que les justificatifs liés au formsemestre)
|
||||
formsemestre_id = request.args.get("formsemestre_id", "")
|
||||
formsemestre = None
|
||||
if formsemestre_id:
|
||||
try:
|
||||
formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id)
|
||||
@ -241,19 +204,71 @@ def bilan_dept():
|
||||
except AttributeError:
|
||||
formsemestre_id = ""
|
||||
|
||||
# Peuplement du template jinja
|
||||
H.append(
|
||||
render_template(
|
||||
"assiduites/pages/bilan_dept.j2",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
annee=annee,
|
||||
annees=annees_str,
|
||||
formsemestre_id=formsemestre_id,
|
||||
group_id=request.args.get("group_id", ""),
|
||||
# <=> Génération du tableau <=>
|
||||
|
||||
# Récupération des étudiants du département / groupe
|
||||
etudids: list[int] = [etud.id for etud in dept.etudiants] # cas département
|
||||
group_ids = request.args.get("group_ids", "")
|
||||
if group_ids and formsemestre:
|
||||
groups_infos = sco_groups_view.DisplayedGroupsInfos(
|
||||
group_ids.split(","),
|
||||
formsemestre_id=formsemestre.id,
|
||||
select_all_when_unspecified=True,
|
||||
)
|
||||
|
||||
if groups_infos.members:
|
||||
etudids = [m["etudid"] for m in groups_infos.members]
|
||||
|
||||
# justificatifs (en attente ou modifiés avec les semestres associés)
|
||||
justificatifs_query: Query = Justificatif.query.filter(
|
||||
Justificatif.etat.in_(
|
||||
[scu.EtatJustificatif.ATTENTE, scu.EtatJustificatif.MODIFIE]
|
||||
),
|
||||
Justificatif.etudid.in_(etudids),
|
||||
)
|
||||
# Filtrage par semestre si formsemestre_id != ""
|
||||
if formsemestre:
|
||||
justificatifs_query = justificatifs_query.filter(
|
||||
Justificatif.date_debut >= formsemestre.date_debut,
|
||||
Justificatif.date_debut <= formsemestre.date_fin,
|
||||
)
|
||||
|
||||
data = liste_assi.AssiJustifData(
|
||||
assiduites_query=None,
|
||||
justificatifs_query=justificatifs_query,
|
||||
)
|
||||
|
||||
fname: str = "Bilan Département"
|
||||
cache_key: str = "tableau-dept"
|
||||
titre: str = "Justificatifs en attente ou modifiés"
|
||||
|
||||
if formsemestre:
|
||||
fname += f" {formsemestre.titre_annee()}"
|
||||
cache_key += f"-{formsemestre.id}"
|
||||
titre += f" {formsemestre.titre_annee()}"
|
||||
|
||||
if group_ids:
|
||||
cache_key += f" {group_ids}"
|
||||
|
||||
table = _prepare_tableau(
|
||||
data,
|
||||
afficher_etu=True,
|
||||
filename=fname,
|
||||
titre=titre,
|
||||
cache_key=cache_key,
|
||||
)
|
||||
|
||||
if not table[0]:
|
||||
return table[1]
|
||||
|
||||
# Peuplement du template jinja
|
||||
return render_template(
|
||||
"assiduites/pages/bilan_dept.j2",
|
||||
tableau=table[1],
|
||||
search_etud=sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"),
|
||||
billets=billets,
|
||||
sco=ScoData(formsemestre=formsemestre),
|
||||
)
|
||||
H.append(html_sco_header.sco_footer())
|
||||
return "\n".join(H)
|
||||
|
||||
|
||||
@bp.route("/ajout_assiduite_etud", methods=["GET", "POST"])
|
||||
@ -601,6 +616,29 @@ def bilan_etud():
|
||||
sco_preferences.get_preference("assi_metrique", dept_id=g.scodoc_dept_id),
|
||||
)
|
||||
|
||||
# Récupération des assiduités et justificatifs de l'étudiant
|
||||
data = liste_assi.AssiJustifData(
|
||||
etud.assiduites.filter(
|
||||
Assiduite.etat != scu.EtatAssiduite.PRESENT, Assiduite.est_just == False
|
||||
),
|
||||
etud.justificatifs.filter(
|
||||
Justificatif.etat.in_(
|
||||
[scu.EtatJustificatif.ATTENTE, scu.EtatJustificatif.MODIFIE]
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
table = _prepare_tableau(
|
||||
data,
|
||||
afficher_etu=False,
|
||||
filename=f"Bilan assiduité {etud.nomprenom}",
|
||||
titre="Bilan de l'assiduité de l'étudiant",
|
||||
cache_key=f"tableau-etud-{etud.id}-bilan",
|
||||
)
|
||||
|
||||
if not table[0]:
|
||||
return table[1]
|
||||
|
||||
# Génération de la page
|
||||
return HTMLBuilder(
|
||||
header,
|
||||
@ -615,6 +653,7 @@ def bilan_etud():
|
||||
"assi_limit_annee",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
),
|
||||
tableau=table[1],
|
||||
),
|
||||
).build()
|
||||
|
||||
@ -1599,18 +1638,7 @@ def tableau_assiduite_actions():
|
||||
|
||||
if obj_type == "assiduite":
|
||||
# Construction du menu module
|
||||
# XXX ca ne va pas car cela ne prend qu'un semestre
|
||||
# TODO reprendre le menu de la page ajout_assiduite_etud
|
||||
formsemestre = objet.get_formsemestre()
|
||||
if formsemestre:
|
||||
if objet.moduleimpl_id is not None:
|
||||
module = objet.moduleimpl_id
|
||||
elif objet.external_data is not None:
|
||||
module = objet.external_data.get("module", "")
|
||||
module = module.lower() if isinstance(module, str) else module
|
||||
module = _module_selector(formsemestre, module)
|
||||
else:
|
||||
module = "pas de semestre correspondant"
|
||||
module = _module_selector_multiple(objet.etudiant, objet.moduleimpl_id)
|
||||
|
||||
return render_template(
|
||||
"assiduites/pages/tableau_assiduite_actions.j2",
|
||||
@ -1818,7 +1846,7 @@ def signal_assiduites_diff():
|
||||
)
|
||||
date_fin: datetime.date = date_deb + datetime.timedelta(days=6)
|
||||
|
||||
etudiants: list[dict] = []
|
||||
etudiants: list[Identite] = []
|
||||
|
||||
# --- Vérification de la date ---
|
||||
real_date = scu.is_iso_formated(date, True).date()
|
||||
@ -1846,15 +1874,9 @@ def signal_assiduites_diff():
|
||||
|
||||
# Récupération des étudiants
|
||||
etudiants.extend(
|
||||
[
|
||||
sco_etud.get_etud_info(etudid=m["etudid"], filled=True)[0]
|
||||
for m in groups_infos.members
|
||||
]
|
||||
[Identite.get_etud(etudid=m["etudid"]) for m in groups_infos.members]
|
||||
)
|
||||
# XXX utiliser des instances d'Identite et non des dict
|
||||
# puis trier avec etud.sort_key
|
||||
# afin de bien prendre en compte nom usuel etc
|
||||
etudiants = list(sorted(etudiants, key=lambda x: x["nom"]))
|
||||
etudiants = list(sorted(etudiants, key=lambda etud: etud.sort_key))
|
||||
|
||||
# Génération de l'HTML
|
||||
|
||||
@ -1962,9 +1984,7 @@ def signale_evaluation_abs(etudid: int = None, evaluation_id: int = None):
|
||||
"assiduites.ajout_assiduite_etud",
|
||||
etudid=etudid,
|
||||
evaluation_id=evaluation.id,
|
||||
date_deb=evaluation.date_debut.strftime(
|
||||
"%Y-%m-%dT%H:%M:%S"
|
||||
),
|
||||
date_deb=evaluation.date_debut.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
date_fin=evaluation.date_fin.strftime("%Y-%m-%dT%H:%M:%S"),
|
||||
moduleimpl_id=evaluation.moduleimpl.id,
|
||||
saisie_eval="true",
|
||||
@ -2234,6 +2254,32 @@ def _module_selector(formsemestre: FormSemestre, moduleimpl_id: int = None) -> s
|
||||
)
|
||||
|
||||
|
||||
def _module_selector_multiple(
|
||||
etud: Identite, moduleimpl_id: int = None, only_form: FormSemestre = None
|
||||
) -> str:
|
||||
modimpls_by_formsemestre = etud.get_modimpls_by_formsemestre(scu.annee_scolaire())
|
||||
choices = {}
|
||||
for formsemestre_id in modimpls_by_formsemestre:
|
||||
formsemestre: FormSemestre = FormSemestre.query.get(formsemestre_id)
|
||||
if only_form is not None and formsemestre != only_form:
|
||||
continue
|
||||
# indique le nom du semestre dans le menu (optgroup)
|
||||
choices[formsemestre.titre_annee()] = [
|
||||
{
|
||||
"moduleimpl_id": m.id,
|
||||
"name": f"{m.module.code} {m.module.abbrev or m.module.titre or ''}",
|
||||
}
|
||||
for m in modimpls_by_formsemestre[formsemestre_id]
|
||||
if m.module.ue.type == UE_STANDARD
|
||||
]
|
||||
|
||||
return render_template(
|
||||
"assiduites/widgets/moduleimpl_selector_multiple.j2",
|
||||
choices=choices,
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
)
|
||||
|
||||
|
||||
def _dynamic_module_selector() -> str:
|
||||
"""
|
||||
_dynamic_module_selector retourne l'html/css/javascript du selecteur de module dynamique
|
||||
@ -2630,6 +2676,8 @@ def _generate_assiduite_bubble(assiduite: Assiduite) -> str:
|
||||
# Récupérer informations saisie
|
||||
saisie: str = assiduite.get_saisie()
|
||||
|
||||
motif: str = assiduite.description if assiduite.description else ""
|
||||
|
||||
return render_template(
|
||||
"assiduites/widgets/assiduite_bubble.j2",
|
||||
moduleimpl=moduleimpl_infos,
|
||||
@ -2637,4 +2685,5 @@ def _generate_assiduite_bubble(assiduite: Assiduite) -> str:
|
||||
date_debut=assiduite.date_debut.strftime("%d/%m/%Y %H:%M"),
|
||||
date_fin=assiduite.date_fin.strftime("%d/%m/%Y %H:%M"),
|
||||
saisie=saisie,
|
||||
motif=motif,
|
||||
)
|
||||
|
@ -557,50 +557,65 @@ def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justific
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif).count() == 5
|
||||
), "Filtrage 'Toute Date' mauvais 1"
|
||||
|
||||
date = scu.localize_datetime("2022-09-01T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||
== 5
|
||||
), "Filtrage 'Toute Date' mauvais 2"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T08:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T08:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||
== 5
|
||||
), "Filtrage 'date début' mauvais 3"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T08:00:01+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T08:00:01+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||
== 5
|
||||
), "Filtrage 'date début' mauvais 4"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
|
||||
== 4
|
||||
), "Filtrage 'date début' mauvais 5"
|
||||
|
||||
date = scu.localize_datetime("2022-09-01T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||
== 0
|
||||
), "Filtrage 'date fin' mauvais 6"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T08:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T08:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||
== 1
|
||||
), "Filtrage 'date fin' mauvais 7"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T10:00:01+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T10:00:01+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||
== 2
|
||||
), "Filtrage 'date fin' mauvais 8"
|
||||
|
||||
date = scu.localize_datetime("2023-01-03T12:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2023-01-03T12:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
|
||||
== 5
|
||||
@ -624,8 +639,12 @@ def editer_supprimer_justificatif(etud: Identite):
|
||||
# Modification de l'état
|
||||
justi.etat = scu.EtatJustificatif.MODIFIE
|
||||
# Modification du moduleimpl
|
||||
justi.date_debut = scu.localize_datetime("2023-02-03T11:00:01+01:00")
|
||||
justi.date_fin = scu.localize_datetime("2023-02-03T12:00:01+01:00")
|
||||
justi.date_debut = scu.localize_datetime(
|
||||
scu.is_iso_formated("2023-02-03T11:00:01+01:00", convert=True)
|
||||
)
|
||||
justi.date_fin = scu.localize_datetime(
|
||||
scu.is_iso_formated("2023-02-03T12:00:01+01:00", convert=True)
|
||||
)
|
||||
|
||||
db.session.add(justi)
|
||||
db.session.commit()
|
||||
@ -639,7 +658,9 @@ def editer_supprimer_justificatif(etud: Identite):
|
||||
scass.filter_by_date(
|
||||
etud.justificatifs,
|
||||
Justificatif,
|
||||
date_deb=scu.localize_datetime("2023-02-01T11:00:00+01:00"),
|
||||
date_deb=scu.localize_datetime(
|
||||
scu.is_iso_formated("2023-02-01T11:00:00+01:00", convert=True)
|
||||
),
|
||||
).count()
|
||||
== 1
|
||||
), "Edition de justificatif mauvais 2"
|
||||
@ -930,44 +951,60 @@ def verifier_comptage_et_filtrage_assiduites(
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite).count() == 7
|
||||
), "Filtrage 'Date début' mauvais 1"
|
||||
|
||||
date = scu.localize_datetime("2022-09-01T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
|
||||
), "Filtrage 'Date début' mauvais 2"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
|
||||
), "Filtrage 'Date début' mauvais 3"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T16:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T16:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4
|
||||
), "Filtrage 'Date début' mauvais 4"
|
||||
|
||||
# Date Fin
|
||||
|
||||
date = scu.localize_datetime("2022-09-01T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0
|
||||
), "Filtrage 'Date fin' mauvais 1"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T10:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1
|
||||
), "Filtrage 'Date fin' mauvais 2"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T10:00:01+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T10:00:01+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2
|
||||
), "Filtrage 'Date fin' mauvais 3"
|
||||
|
||||
date = scu.localize_datetime("2022-09-05T16:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2022-09-05T16:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3
|
||||
), "Filtrage 'Date fin' mauvais 4"
|
||||
|
||||
date = scu.localize_datetime("2023-01-04T16:00+01:00")
|
||||
date = scu.localize_datetime(
|
||||
scu.is_iso_formated("2023-01-04T16:00+01:00", convert=True)
|
||||
)
|
||||
assert (
|
||||
scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 7
|
||||
), "Filtrage 'Date fin' mauvais 5"
|
||||
|
Loading…
Reference in New Issue
Block a user