This commit is contained in:
Emmanuel Viennet 2023-09-12 19:08:43 +02:00
commit 6542f691f8
21 changed files with 426 additions and 129 deletions

View File

@ -27,7 +27,7 @@ from app.models import (
Justificatif, Justificatif,
) )
from flask_sqlalchemy.query import Query from flask_sqlalchemy.query import Query
from app.models.assiduites import get_assiduites_justif from app.models.assiduites import get_assiduites_justif, get_justifs_from_date
from app.scodoc.sco_exceptions import ScoValueError from app.scodoc.sco_exceptions import ScoValueError
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
from app.scodoc.sco_utils import json_error from app.scodoc.sco_utils import json_error
@ -728,7 +728,6 @@ def assiduite_edit(assiduite_id: int):
assiduite_unique.etudiant.id, assiduite_unique.etudiant.id,
msg=f"assiduite: modif {assiduite_unique}", msg=f"assiduite: modif {assiduite_unique}",
) )
db.session.add(assiduite_unique)
db.session.commit() db.session.commit()
scass.simple_invalidate_cache(assiduite_unique.to_dict()) scass.simple_invalidate_cache(assiduite_unique.to_dict())
@ -848,15 +847,23 @@ def _edit_singular(assiduite_unique, data):
# Cas 3 : desc # Cas 3 : desc
desc = data.get("desc", False) desc = data.get("desc", False)
if desc is not False: if desc is not False:
assiduite_unique.desc = desc assiduite_unique.description = desc
# Cas 4 : est_just # Cas 4 : est_just
est_just = data.get("est_just") if assiduite_unique.etat == scu.EtatAssiduite.PRESENT:
if est_just is not None: assiduite_unique.est_just = False
if not isinstance(est_just, bool): else:
errors.append("param 'est_just' : booléen non reconnu") assiduite_unique.est_just = (
else: len(
assiduite_unique.est_just = est_just get_justifs_from_date(
assiduite_unique.etudiant.id,
assiduite_unique.date_debut,
assiduite_unique.date_fin,
valid=True,
)
)
> 0
)
if errors: if errors:
err: str = ", ".join(errors) err: str = ", ".join(errors)
@ -1024,6 +1031,19 @@ def _filter_manager(requested, assiduites_query: Query) -> Query:
if user_id is not False: if user_id is not False:
assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id) assiduites_query: Query = scass.filter_by_user_id(assiduites_query, user_id)
order = requested.args.get("order", None)
if order is not None:
assiduites_query: Query = assiduites_query.order_by(Assiduite.date_debut.desc())
courant = requested.args.get("courant", None)
if courant is not None:
annee: int = scu.annee_scolaire()
assiduites_query: Query = assiduites_query.filter(
Assiduite.date_debut >= scu.date_debut_anne_scolaire(annee),
Assiduite.date_fin <= scu.date_fin_anne_scolaire(annee),
)
return assiduites_query return assiduites_query

View File

@ -130,6 +130,8 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
@api_web_bp.route( @api_web_bp.route(
"/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True} "/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True}
) )
@bp.route("/justificatifs/dept/<int:dept_id>", defaults={"with_query": False})
@bp.route("/justificatifs/dept/<int:dept_id>/query", defaults={"with_query": True})
@login_required @login_required
@scodoc @scodoc
@as_json @as_json
@ -151,6 +153,48 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
return data_set return data_set
@bp.route(
"/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
)
@api_web_bp.route(
"/justificatifs/formsemestre/<int:formsemestre_id>", defaults={"with_query": False}
)
@bp.route(
"/justificatifs/formsemestre/<int:formsemestre_id>/query",
defaults={"with_query": True},
)
@api_web_bp.route(
"/justificatifs/formsemestre/<int:formsemestre_id>/query",
defaults={"with_query": True},
)
@login_required
@scodoc
@as_json
@permission_required(Permission.ScoView)
def justificatifs_formsemestre(formsemestre_id: int, with_query: bool = False):
"""Retourne tous les justificatifs du formsemestre"""
formsemestre: FormSemestre = None
formsemestre_id = int(formsemestre_id)
formsemestre = FormSemestre.query.filter_by(id=formsemestre_id).first()
if formsemestre is None:
return json_error(404, "le paramètre 'formsemestre_id' n'existe pas")
justificatifs_query = scass.filter_by_formsemestre(
Justificatif.query, Justificatif, formsemestre
)
if with_query:
justificatifs_query = _filter_manager(request, justificatifs_query)
data_set: list[dict] = []
for justi in justificatifs_query.all():
data = justi.to_dict(format_api=True)
data_set.append(data)
return data_set
@bp.route("/justificatif/<int:etudid>/create", methods=["POST"]) @bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
@api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"]) @api_web_bp.route("/justificatif/<int:etudid>/create", methods=["POST"])
@bp.route("/justificatif/etudid/<etudid>/create", methods=["POST"]) @bp.route("/justificatif/etudid/<etudid>/create", methods=["POST"])
@ -696,4 +740,19 @@ def _filter_manager(requested, justificatifs_query):
justificatifs_query, Justificatif, formsemestre justificatifs_query, Justificatif, formsemestre
) )
order = requested.args.get("order", None)
if order is not None:
justificatifs_query: Query = justificatifs_query.order_by(
Justificatif.date_debut.desc()
)
courant = requested.args.get("courant", None)
if courant is not None:
annee: int = scu.annee_scolaire()
justificatifs_query: Query = justificatifs_query.filter(
Justificatif.date_debut >= scu.date_debut_anne_scolaire(annee),
Justificatif.date_fin <= scu.date_fin_anne_scolaire(annee),
)
return justificatifs_query return justificatifs_query

View File

@ -134,7 +134,10 @@ class Assiduite(db.Model):
if not est_just: if not est_just:
est_just = ( est_just = (
len(_get_assiduites_justif(etud.etudid, date_debut, date_fin)) > 0 len(
get_justifs_from_date(etud.etudid, date_debut, date_fin, valid=True)
)
> 0
) )
if moduleimpl is not None: if moduleimpl is not None:
@ -375,16 +378,23 @@ def compute_assiduites_justified(
def get_assiduites_justif(assiduite_id: int, long: bool): def get_assiduites_justif(assiduite_id: int, long: bool):
assi: Assiduite = Assiduite.query.get_or_404(assiduite_id) assi: Assiduite = Assiduite.query.get_or_404(assiduite_id)
return _get_assiduites_justif(assi.etudid, assi.date_debut, assi.date_fin, long) return get_justifs_from_date(assi.etudid, assi.date_debut, assi.date_fin, long)
def _get_assiduites_justif( def get_justifs_from_date(
etudid: int, date_debut: datetime, date_fin: datetime, long: bool = False etudid: int,
date_debut: datetime,
date_fin: datetime,
long: bool = False,
valid: bool = False,
): ):
justifs: Justificatif = Justificatif.query.filter( justifs: Query = Justificatif.query.filter(
Justificatif.etudid == etudid, Justificatif.etudid == etudid,
Justificatif.date_debut <= date_debut, Justificatif.date_debut <= date_debut,
Justificatif.date_fin >= date_fin, Justificatif.date_fin >= date_fin,
) )
if valid:
justifs = justifs.filter(Justificatif.etat == EtatJustificatif.VALIDE)
return [j.justif_id if not long else j.to_dict(True) for j in justifs] return [j.justif_id if not long else j.to_dict(True) for j in justifs]

View File

@ -311,7 +311,7 @@ def filter_by_date(
) )
def filter_justificatifs_by_etat(justificatifs: Justificatif, etat: str) -> Query: def filter_justificatifs_by_etat(justificatifs: Query, etat: str) -> Query:
""" """
Filtrage d'une collection de justificatifs en fonction de leur état Filtrage d'une collection de justificatifs en fonction de leur état
""" """

View File

@ -610,6 +610,17 @@ class BasePreferences:
}, },
), ),
# Assiduités # Assiduités
(
"assi_limit_annee",
{
"initvalue": 1,
"title": "Ne lister que les assiduités de l'année",
"explanation": "Limite l'affichage des listes d'assiduités et de justificatifs à l'année en cours",
"input_type": "boolcheckbox",
"labels": ["non", "oui"],
"category": "assi",
},
),
( (
"forcer_module", "forcer_module",
{ {

View File

@ -1107,6 +1107,7 @@ def _get_sorted_etuds(evaluation: Evaluation, etudids: list, formsemestre_id: in
evaluation.date_debut.date().isoformat() if evaluation.date_debut else "" evaluation.date_debut.date().isoformat() if evaluation.date_debut else ""
) )
warn_abs_lst = [] warn_abs_lst = []
# XXX TODO-ASSIDUITE (issue #686)
if evaluation.is_matin(): if evaluation.is_matin():
nbabs = 0 # TODO-ASSIDUITE sco_abs.count_abs(etudid, jour_iso, jour_iso, matin=True) 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) nbabsjust = 0 # TODO-ASSIDUITE sco_abs.count_abs_just(etudid, jour_iso, jour_iso, matin=True)

View File

@ -136,6 +136,8 @@
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
margin: 0 5%; margin: 0 5%;
cursor: pointer;
} }
.etud_row.def .nom::after, .etud_row.def .nom::after,
@ -268,6 +270,7 @@
background-size: cover; background-size: cover;
} }
.rbtn.present::before { .rbtn.present::before {
background-image: url(../icons/present.svg); background-image: url(../icons/present.svg);
} }
@ -285,8 +288,8 @@
} }
.rbtn:checked:before { .rbtn:checked:before {
outline: 3px solid #7059FF; outline: 5px solid #7059FF;
border-radius: 5px; border-radius: 50%;
} }
.rbtn:focus { .rbtn:focus {

View File

@ -1065,8 +1065,22 @@ function actualizeEtudAssiduite(etudid) {
}); });
} }
function getAllAssiduitesFromEtud(etudid, action) { function getAllAssiduitesFromEtud(
const url_api = getUrl() + `/api/assiduites/${etudid}`; etudid,
action,
order = false,
justifs = false,
courant = false
) {
const url_api =
getUrl() +
`/api/assiduites/${etudid}${
order
? "/query?order%°"
.replace("%", justifs ? "&with_justifs" : "")
.replace("°", courant ? "&courant" : "")
: ""
}`;
$.ajax({ $.ajax({
async: true, async: true,
@ -1240,12 +1254,10 @@ function generateEtudRow(
<img class="pdp" src="${pdp_url}"> <img class="pdp" src="${pdp_url}">
<div class="name_set"> <a class="name_set" href="BilanEtud?etudid=${etud.id}">
<h4 class="nom">${etud.nom}</h4> <h4 class="nom">${etud.nom}</h4>
<h5 class="prenom">${etud.prenom}</h5> <h5 class="prenom">${etud.prenom}</h5>
</a>
</div>
</div> </div>
<div class="assiduites_bar"> <div class="assiduites_bar">
@ -1558,20 +1570,18 @@ function fastJustify(assiduite) {
//créer justificatif //créer justificatif
const justif = { const justif = {
date_debut: new moment.tz(assiduite.date_debut, TIMEZONE) date_debut: new moment.tz(assiduite.date_debut, TIMEZONE).format(),
.add(1, "s") date_fin: new moment.tz(assiduite.date_fin, TIMEZONE).format(),
.format(),
date_fin: new moment.tz(assiduite.date_fin, TIMEZONE)
.subtract(1, "s")
.format(),
raison: raison, raison: raison,
etat: etat, etat: etat,
}; };
createJustificatif(justif); createJustificatif(justif);
// justifyAssiduite(assiduite.assiduite_id, true);
generateAllEtudRow(); generateAllEtudRow();
try {
loadAll();
} catch {}
}; };
const content = document.createElement("fieldset"); const content = document.createElement("fieldset");
@ -1634,8 +1644,17 @@ function createJustificatif(justif, success = () => {}) {
}); });
} }
function getAllJustificatifsFromEtud(etudid, action) { function getAllJustificatifsFromEtud(
const url_api = getUrl() + `/api/justificatifs/${etudid}`; etudid,
action,
order = false,
courant = false
) {
const url_api =
getUrl() +
`/api/justificatifs/${etudid}${
order ? "/query?order°".replace("°", courant ? "&courant" : "") : ""
}`;
$.ajax({ $.ajax({
async: true, async: true,
type: "GET", type: "GET",

View File

@ -114,9 +114,18 @@ class RowAssi(tb.Row):
compte_justificatifs = scass.filter_by_date( compte_justificatifs = scass.filter_by_date(
etud.justificatifs, Justificatif, self.dates[0], self.dates[1] etud.justificatifs, Justificatif, self.dates[0], self.dates[1]
).count() )
self.add_cell("justificatifs", "Justificatifs", f"{compte_justificatifs}") compte_justificatifs_att = compte_justificatifs.filter(Justificatif.etat == 2)
self.add_cell(
"justificatifs_att",
"Justificatifs en Attente",
f"{compte_justificatifs_att.count()}",
)
self.add_cell(
"justificatifs", "Justificatifs", f"{compte_justificatifs.count()}"
)
def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]: def _get_etud_stats(self, etud: Identite) -> dict[str, list[str, float, float]]:
retour: dict[str, tuple[str, float, float]] = { retour: dict[str, tuple[str, float, float]] = {

View File

@ -19,8 +19,9 @@
<div class="justi-label"> <div class="justi-label">
<legend for="justi_date_debut" required>Date de début</legend> <legend for="justi_date_debut" required>Date de début</legend>
<input type="datetime-local" name="justi_date_debut" id="justi_date_debut"> <input type="datetime-local" name="justi_date_debut" id="justi_date_debut">
<span>Journée entière</span> <input type="checkbox" name="justi_journee" id="justi_journee">
</div> </div>
<div class="justi-label"> <div class="justi-label" id="date_fin">
<legend for="justi_date_fin" required>Date de fin</legend> <legend for="justi_date_fin" required>Date de fin</legend>
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin"> <input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
</div> </div>
@ -110,16 +111,15 @@
function validateFields() { function validateFields() {
const field = document.querySelector('.justi-form') const field = document.querySelector('.justi-form')
const in_date_debut = field.querySelector('#justi_date_debut'); const { deb, fin } = getDates()
const in_date_fin = field.querySelector('#justi_date_fin');
if (in_date_debut.value == "" || in_date_fin.value == "") { if (deb.value == "" || fin.value == "") {
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin."), "", color = "crimson"); openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin."), "", color = "crimson");
return false; return false;
} }
const date_debut = moment.tz(in_date_debut.value, TIMEZONE); const date_debut = moment.tz(deb.value, TIMEZONE);
const date_fin = moment.tz(in_date_fin.value, TIMEZONE); const date_fin = moment.tz(fin.value, TIMEZONE);
if (date_fin.isBefore(date_debut)) { 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"); openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
@ -132,14 +132,14 @@
function fieldsToJustificatif() { function fieldsToJustificatif() {
const field = document.querySelector('.justi-form') const field = document.querySelector('.justi-form')
const date_debut = field.querySelector('#justi_date_debut').value; const { deb, fin } = getDates()
const date_fin = field.querySelector('#justi_date_fin').value;
const etat = field.querySelector('#justi_etat').value; const etat = field.querySelector('#justi_etat').value;
const raison = field.querySelector('#justi_raison').value; const raison = field.querySelector('#justi_raison').value;
return { return {
date_debut: date_debut, date_debut: deb,
date_fin: date_fin, date_fin: fin,
etat: etat, etat: etat,
raison: raison, raison: raison,
} }
@ -218,11 +218,43 @@
} }
function dayOnly() {
if (document.getElementById('justi_journee').checked) {
document.getElementById("date_fin").style.display = "none";
document.getElementById("justi_date_debut").type = "date"
} else {
document.getElementById("date_fin").style.display = "block";
document.getElementById("justi_date_debut").type = "datetime-local"
}
}
function getDates() {
if (document.getElementById('justi_journee').checked) {
const date_str = document.getElementById("justi_date_debut").value
return {
"deb": `${date_str}T${assi_morning}`,
"fin": `${date_str}T${assi_evening}`,
}
}
return {
"deb": document.getElementById("justi_date_debut").value,
"fin": document.getElementById("justi_date_fin").value,
}
}
const etudid = {{ sco.etud.id }}; 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}}';
window.onload = () => { window.onload = () => {
loadAll(); loadAll();
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
dayOnly()
} }
</script> </script>
{% endblock pageContent %} {% endblock pageContent %}

View File

@ -29,6 +29,7 @@
</div> </div>
<script> <script>
function loadAll() { function loadAll() {
generate(defAnnee) generate(defAnnee)
} }
@ -57,7 +58,7 @@
} }
bornes = { bornes = {
deb: `${annee}-09-01T00:00`, deb: `${annee}-09-01T00:00`,
fin: `${annee + 1}-06-30T23:59` fin: `${annee + 1}-08-31T23:59`
} }
defAnnee = annee; defAnnee = annee;
@ -75,10 +76,14 @@
let defAnnee = {{ annee }}; let defAnnee = {{ annee }};
let bornes = { let bornes = {
deb: `${defAnnee}-09-01T00:00`, deb: `${defAnnee}-09-01T00:00`,
fin: `${defAnnee + 1}-06-30T23:59` fin: `${defAnnee + 1}-08-31T23:59`
} }
const dept_id = {{ dept_id }}; const dept_id = {{ dept_id }};
let annees = {{ annees | safe}}
annees = annees.filter((x, i) => annees.indexOf(x) === i)
window.addEventListener('load', () => { window.addEventListener('load', () => {
filterJustificatifs = { filterJustificatifs = {
@ -99,15 +104,16 @@
} }
} }
const select = document.querySelector('#annee'); const select = document.querySelector('#annee');
for (let i = defAnnee + 1; i > defAnnee - 6; i--) {
annees.forEach((a) => {
const opt = document.createElement("option"); const opt = document.createElement("option");
opt.value = i + "", opt.value = a + "",
opt.textContent = i + ""; opt.textContent = `${a} - ${a + 1}`;
if (i === defAnnee) { if (a === defAnnee) {
opt.selected = true; opt.selected = true;
} }
select.appendChild(opt) select.appendChild(opt)
} })
setterAnnee(defAnnee) setterAnnee(defAnnee)
}) })

View File

@ -266,6 +266,9 @@
const assi_date_debut = "{{date_debut}}"; const assi_date_debut = "{{date_debut}}";
const assi_date_fin = "{{date_fin}}"; const assi_date_fin = "{{date_fin}}";
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
window.addEventListener('load', () => { window.addEventListener('load', () => {
filterAssiduites = { filterAssiduites = {
"columns": [ "columns": [

View File

@ -354,5 +354,7 @@
setterAnnee(defAnnee) setterAnnee(defAnnee)
}; };
function isCalendrier() { return true }
</script> </script>
{% endblock pageContent %} {% endblock pageContent %}

View File

@ -48,8 +48,43 @@
<script> <script>
const etudid = {{ sco.etud.id }} const etudid = {{ sco.etud.id }}
const assiduite_unique_id = {{ assi_id }};
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
function wayForFilter() {
if (typeof assiduites[etudid] !== "undefined") {
console.log("Done")
let assiduite = assiduites[etudid].filter((a) => { return a.assiduite_id == assiduite_unique_id });
if (assiduite) {
assiduite = assiduite[0]
filterAssiduites["filters"] = {
"obj_id": [
assiduite.assiduite_id,
]
}
const obj_ids = assiduite.justificatifs ? assiduite.justificatifs.map((j) => { return j.justif_id }) : []
filterJustificatifs["filters"] = {
"obj_id": obj_ids
}
loadAll();
}
} else {
setTimeout(wayForFilter, 250)
}
}
window.onload = () => { window.onload = () => {
loadAll(); loadAll();
if (assiduite_unique_id != -1) {
wayForFilter()
}
} }
</script> </script>

View File

@ -71,6 +71,11 @@
updateSelectedSelect(getCurrentAssiduiteModuleImplId()); updateSelectedSelect(getCurrentAssiduiteModuleImplId());
updateJustifyBtn(); updateJustifyBtn();
} }
try {
if (isCalendrier()) {
window.location = `ListeAssiduitesEtud?etudid=${etudid}&assiduite_id=${assiduité.assiduite_id}`
}
} catch { }
}); });
//ajouter affichage assiduites on over //ajouter affichage assiduites on over
setupAssiduiteBuble(block, assiduité); setupAssiduiteBuble(block, assiduité);

View File

@ -147,7 +147,7 @@
<span class="obj-content">${etat}</span> <span class="obj-content">${etat}</span>
</div> </div>
<div id="user" class="obj-part"> <div id="user" class="obj-part">
<span class="obj-title">Créer par</span> <span class="obj-title">Créée par</span>
<span class="obj-content">${user}</span> <span class="obj-content">${user}</span>
</div> </div>
</div> </div>
@ -239,7 +239,7 @@
edit = setModuleImplId(edit, module); edit = setModuleImplId(edit, module);
fullEditAssiduites(data.assiduite_id, edit, () => { fullEditAssiduites(data.assiduite_id, edit, () => {
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { } loadAll();
}) })

View File

@ -18,6 +18,9 @@
document.addEventListener("click", () => { document.addEventListener("click", () => {
contextMenu.style.display = "none"; contextMenu.style.display = "none";
if (contextMenu.childElementCount > 3) {
contextMenu.removeChild(contextMenu.lastElementChild)
}
}); });
editOption.addEventListener("click", () => { editOption.addEventListener("click", () => {
@ -94,6 +97,11 @@
} }
} }
if (k == "obj_id") {
const obj_id = el.assiduite_id || el.justif_id;
return f.obj_id.includes(obj_id)
}
return true; return true;
}) })
@ -150,7 +158,7 @@
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => { paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
if (currentPageAssiduites > 1) { if (currentPageAssiduites > 1) {
currentPageAssiduites--; currentPageAssiduites--;
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
assiduiteCallBack(array); assiduiteCallBack(array);
} }
@ -159,7 +167,7 @@
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => { paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
if (currentPageAssiduites < totalPages) { if (currentPageAssiduites < totalPages) {
currentPageAssiduites++; currentPageAssiduites++;
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
assiduiteCallBack(array); assiduiteCallBack(array);
} }
}) })
@ -199,8 +207,12 @@
if (assi) { if (assi) {
paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton) paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
if (i == currentPageAssiduites)
paginationContainerAssiduites.querySelector('#paginationAssi').value = i + "";
} else { } else {
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton) paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
if (i == currentPageJustificatifs)
paginationContainerJustificatifs.querySelector('#paginationJusti').value = i + "";
} }
} }
updateActivePaginationButton(assi); updateActivePaginationButton(assi);
@ -230,8 +242,8 @@
} }
function loadAll() { function loadAll() {
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { } try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack, true, true, assi_limit_annee) } catch (_) { }
try { getAllJustificatifsFromEtud(etudid, justificatifCallBack) } catch (_) { } try { getAllJustificatifsFromEtud(etudid, justificatifCallBack, true, assi_limit_annee) } catch (_) { }
} }
function order(keyword, callback = () => { }, el, assi = true) { function order(keyword, callback = () => { }, el, assi = true) {
@ -641,6 +653,27 @@
contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`; contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
contextMenu.style.left = `${e.clientX}px`; contextMenu.style.left = `${e.clientX}px`;
contextMenu.style.display = "block"; contextMenu.style.display = "block";
if (contextMenu.childElementCount > 3) {
contextMenu.removeChild(contextMenu.lastElementChild)
}
if (selectedRow.getAttribute('type') == "assiduite") {
const li = document.createElement('li')
li.textContent = "Justifier"
li.addEventListener('click', () => {
let obj_id = selectedRow.getAttribute('obj_id');
assiduite = Object.values(assiduites).flat().filter((a) => { return a.assiduite_id == obj_id })
console.log(assiduite[0])
if (assiduite && !assiduite[0].est_just && assiduite[0].etat != "PRESENT") {
fastJustify(assiduite[0])
} else {
openAlertModal("Erreur", document.createTextNode("L'assiduité est déjà justifiée ou ne peut pas l'être."))
}
})
contextMenu.appendChild(li)
}
} }
</script> </script>

View File

@ -169,7 +169,7 @@
<span class="obj-content">${etat}</span> <span class="obj-content">${etat}</span>
</div> </div>
<div id="user" class="obj-part"> <div id="user" class="obj-part">
<span class="obj-title">Créer par</span> <span class="obj-title">Créé par</span>
<span class="obj-content">${user}</span> <span class="obj-content">${user}</span>
</div> </div>
</div> </div>

View File

@ -18,6 +18,8 @@
const period_default = {{ periode_defaut }}; const period_default = {{ periode_defaut }};
let handleMoving = false;
function createTicks() { function createTicks() {
let i = t_start; let i = t_start;
@ -87,72 +89,92 @@
} }
function setupTimeLine(callback) { function timelineMainEvent(event, callback) {
const func_call = callback ? callback : () => { }; const func_call = callback ? callback : () => { };
timelineContainer.addEventListener("mousedown", (event) => {
const startX = event.clientX;
if (event.target === periodTimeLine) { const startX = (event.clientX || event.changedTouches[0].clientX);
const startLeft = parseFloat(periodTimeLine.style.left);
const onMouseMove = (moveEvent) => { if (event.target.classList.contains("period-handle")) {
const deltaX = moveEvent.clientX - startX; const startWidth = parseFloat(periodTimeLine.style.width);
const containerWidth = timelineContainer.clientWidth; const startLeft = parseFloat(periodTimeLine.style.left);
const isLeftHandle = event.target.classList.contains("left");
handleMoving = true
const onMouseMove = (moveEvent) => {
if (!handleMoving) return;
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
const containerWidth = timelineContainer.clientWidth;
const newWidth =
startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100;
if (isLeftHandle) {
const newLeft = startLeft + (deltaX / containerWidth) * 100; const newLeft = startLeft + (deltaX / containerWidth) * 100;
adjustPeriodPosition(newLeft, newWidth);
} else {
adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth);
}
adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width)); updatePeriodTimeLabel();
};
const mouseUp = () => {
snapHandlesToQuarters();
generateAllEtudRow();
updatePeriodTimeLabel(); timelineContainer.removeEventListener("mousemove", onMouseMove);
}; handleMoving = false;
func_call();
document.addEventListener("mousemove", onMouseMove);
document.addEventListener(
"mouseup",
() => {
generateAllEtudRow();
snapHandlesToQuarters();
document.removeEventListener("mousemove", onMouseMove);
func_call();
},
{ once: true }
);
} else if (event.target.classList.contains("period-handle")) {
const startWidth = parseFloat(periodTimeLine.style.width);
const startLeft = parseFloat(periodTimeLine.style.left);
const isLeftHandle = event.target.classList.contains("left");
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - startX;
const containerWidth = timelineContainer.clientWidth;
const newWidth =
startWidth + ((isLeftHandle ? -deltaX : deltaX) / containerWidth) * 100;
if (isLeftHandle) {
const newLeft = startLeft + (deltaX / containerWidth) * 100;
adjustPeriodPosition(newLeft, newWidth);
} else {
adjustPeriodPosition(parseFloat(periodTimeLine.style.left), newWidth);
}
updatePeriodTimeLabel();
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener(
"mouseup",
() => {
snapHandlesToQuarters();
generateAllEtudRow();
document.removeEventListener("mousemove", onMouseMove);
func_call();
},
{ once: true }
);
} }
}); timelineContainer.addEventListener("mousemove", onMouseMove);
timelineContainer.addEventListener("touchmove", onMouseMove);
document.addEventListener(
"mouseup",
mouseUp,
);
document.addEventListener(
"touchend",
mouseUp,
);
} else if (event.target === periodTimeLine) {
const startLeft = parseFloat(periodTimeLine.style.left);
const onMouseMove = (moveEvent) => {
console.warn("move Period")
if (handleMoving) return;
const deltaX = (moveEvent.clientX || moveEvent.changedTouches[0].clientX) - startX;
const containerWidth = timelineContainer.clientWidth;
const newLeft = startLeft + (deltaX / containerWidth) * 100;
adjustPeriodPosition(newLeft, parseFloat(periodTimeLine.style.width));
updatePeriodTimeLabel();
};
const mouseUp = () => {
generateAllEtudRow();
snapHandlesToQuarters();
timelineContainer.removeEventListener("mousemove", onMouseMove);
func_call();
}
timelineContainer.addEventListener("mousemove", onMouseMove);
timelineContainer.addEventListener("touchmove", onMouseMove);
document.addEventListener(
"mouseup",
mouseUp,
{ once: true }
);
document.addEventListener(
"touchend",
mouseUp,
{ once: true }
);
}
}
function setupTimeLine(callback) {
timelineContainer.addEventListener("mousedown", (e) => { timelineMainEvent(e, callback) });
timelineContainer.addEventListener("touchstart", (e) => { timelineMainEvent(e, callback) });
} }
function adjustPeriodPosition(newLeft, newWidth) { function adjustPeriodPosition(newLeft, newWidth) {

View File

@ -162,24 +162,35 @@ def index_html():
"""<p class="help">Pour signaler, annuler ou justifier une assiduité pour un seul étudiant, """<p class="help">Pour signaler, annuler ou justifier une assiduité pour un seul étudiant,
choisissez d'abord le concerné:</p>""" choisissez d'abord le concerné:</p>"""
) )
H.append(sco_find_etud.form_search_etud()) H.append(sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"))
# if current_user.has_permission( if current_user.has_permission(
# Permission.ScoAbsChange Permission.ScoAbsChange
# ) and sco_preferences.get_preference("handle_billets_abs"): ) and sco_preferences.get_preference("handle_billets_abs"):
# H.append( H.append(
# f""" f"""
# <h2 style="margin-top: 30px;">Billets d'absence</h2> <h2 style="margin-top: 30px;">Billets d'absence</h2>
# <ul><li><a href="{url_for("absences.list_billets", scodoc_dept=g.scodoc_dept) <ul><li><a href="{url_for("absences.list_billets", scodoc_dept=g.scodoc_dept)
# }">Traitement des billets d'absence en attente</a> }">Traitement des billets d'absence en attente</a>
# </li></ul> </li></ul>
# """ """
# ) )
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,
)
annees_str: str = "["
for ann in annees:
annees_str += f"{ann},"
annees_str += "]"
H.append( H.append(
render_template( render_template(
"assiduites/pages/bilan_dept.j2", "assiduites/pages/bilan_dept.j2",
dept_id=g.scodoc_dept_id, dept_id=g.scodoc_dept_id,
annee=scu.annee_scolaire(), annee=scu.annee_scolaire(),
annees=annees_str,
), ),
) )
H.append(html_sco_header.sco_footer()) H.append(html_sco_header.sco_footer())
@ -299,6 +310,8 @@ def liste_assiduites_etud():
if etud.dept_id != g.scodoc_dept_id: if etud.dept_id != g.scodoc_dept_id:
abort(404, "étudiant inexistant dans ce département") abort(404, "étudiant inexistant dans ce département")
assiduite_id: int = request.args.get("assiduite_id", -1)
header: str = html_sco_header.sco_header( header: str = html_sco_header.sco_header(
page_title="Liste des assiduités", page_title="Liste des assiduités",
init_qtip=True, init_qtip=True,
@ -319,6 +332,11 @@ def liste_assiduites_etud():
"assiduites/pages/liste_assiduites.j2", "assiduites/pages/liste_assiduites.j2",
sco=ScoData(etud), sco=ScoData(etud),
date=datetime.date.today().isoformat(), date=datetime.date.today().isoformat(),
assi_id=assiduite_id,
assi_limit_annee=sco_preferences.get_preference(
"assi_limit_annee",
dept_id=g.scodoc_dept_id,
),
), ),
).build() ).build()
@ -371,6 +389,10 @@ def bilan_etud():
date_fin=date_fin, date_fin=date_fin,
assi_metric=assi_metric, assi_metric=assi_metric,
assi_seuil=_get_seuil(), assi_seuil=_get_seuil(),
assi_limit_annee=sco_preferences.get_preference(
"assi_limit_annee",
dept_id=g.scodoc_dept_id,
),
), ),
).build() ).build()
@ -412,6 +434,12 @@ def ajout_justificatif_etud():
render_template( render_template(
"assiduites/pages/ajout_justificatif.j2", "assiduites/pages/ajout_justificatif.j2",
sco=ScoData(etud), sco=ScoData(etud),
assi_limit_annee=sco_preferences.get_preference(
"assi_limit_annee",
dept_id=g.scodoc_dept_id,
),
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
assi_evening=ScoDocSiteConfig.get("assi_evening_time", "18:00"),
), ),
).build() ).build()

View File

@ -157,7 +157,6 @@ def test_general(test_client):
editer_supprimer_justificatif(etuds[0]) editer_supprimer_justificatif(etuds[0])
# XXX TODO-ASSIDUITE (issue #696)
def verif_migration_abs_assiduites(): def verif_migration_abs_assiduites():
"""Vérification que le script de migration fonctionne correctement""" """Vérification que le script de migration fonctionne correctement"""
downgrade_module(assiduites=True, justificatifs=True) downgrade_module(assiduites=True, justificatifs=True)