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