Update opolka/ScoDoc from ScoDoc/ScoDoc #2

Merged
opolka merged 1272 commits from ScoDoc/ScoDoc:master into master 2024-05-27 09:11:04 +02:00
21 changed files with 426 additions and 129 deletions
Showing only changes of commit 6542f691f8 - Show all commits

View File

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

View File

@ -130,6 +130,8 @@ def justificatifs(etudid: int = None, nip=None, ine=None, with_query: bool = Fal
@api_web_bp.route(
"/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
@scodoc
@as_json
@ -151,6 +153,48 @@ def justificatifs_dept(dept_id: int = None, with_query: bool = False):
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"])
@api_web_bp.route("/justificatif/<int: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
)
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

View File

@ -134,7 +134,10 @@ class Assiduite(db.Model):
if not 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:
@ -375,16 +378,23 @@ def compute_assiduites_justified(
def get_assiduites_justif(assiduite_id: int, long: bool):
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(
etudid: int, date_debut: datetime, date_fin: datetime, long: bool = False
def get_justifs_from_date(
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.date_debut <= date_debut,
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]

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
"""

View File

@ -610,6 +610,17 @@ class BasePreferences:
},
),
# 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",
{

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 ""
)
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)

View File

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

View File

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

View File

@ -114,9 +114,18 @@ class RowAssi(tb.Row):
compte_justificatifs = scass.filter_by_date(
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]]:
retour: dict[str, tuple[str, float, float]] = {

View File

@ -19,8 +19,9 @@
<div class="justi-label">
<legend for="justi_date_debut" required>Date de début</legend>
<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 class="justi-label">
<div class="justi-label" id="date_fin">
<legend for="justi_date_fin" required>Date de fin</legend>
<input type="datetime-local" name="justi_date_fin" id="justi_date_fin">
</div>
@ -110,16 +111,15 @@
function validateFields() {
const field = document.querySelector('.justi-form')
const in_date_debut = field.querySelector('#justi_date_debut');
const in_date_fin = field.querySelector('#justi_date_fin');
const { deb, fin } = getDates()
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");
return false;
}
const date_debut = moment.tz(in_date_debut.value, TIMEZONE);
const date_fin = moment.tz(in_date_fin.value, TIMEZONE);
const date_debut = moment.tz(deb.value, TIMEZONE);
const date_fin = moment.tz(fin.value, TIMEZONE);
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");
@ -132,14 +132,14 @@
function fieldsToJustificatif() {
const field = document.querySelector('.justi-form')
const date_debut = field.querySelector('#justi_date_debut').value;
const date_fin = field.querySelector('#justi_date_fin').value;
const { deb, fin } = getDates()
const etat = field.querySelector('#justi_etat').value;
const raison = field.querySelector('#justi_raison').value;
return {
date_debut: date_debut,
date_fin: date_fin,
date_debut: deb,
date_fin: fin,
etat: etat,
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 assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
const assi_morning = '{{assi_morning}}';
const assi_evening = '{{assi_evening}}';
window.onload = () => {
loadAll();
document.getElementById('justi_journee').addEventListener('click', () => { dayOnly() });
dayOnly()
}
</script>
{% endblock pageContent %}

View File

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

View File

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

View File

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

View File

@ -48,8 +48,43 @@
<script>
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 = () => {
loadAll();
if (assiduite_unique_id != -1) {
wayForFilter()
}
}
</script>

View File

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

View File

@ -147,7 +147,7 @@
<span class="obj-content">${etat}</span>
</div>
<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>
</div>
</div>
@ -239,7 +239,7 @@
edit = setModuleImplId(edit, module);
fullEditAssiduites(data.assiduite_id, edit, () => {
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { }
loadAll();
})

View File

@ -18,6 +18,9 @@
document.addEventListener("click", () => {
contextMenu.style.display = "none";
if (contextMenu.childElementCount > 3) {
contextMenu.removeChild(contextMenu.lastElementChild)
}
});
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;
})
@ -150,7 +158,7 @@
paginationContainerAssiduites.querySelector('.pagination_moins').addEventListener('click', () => {
if (currentPageAssiduites > 1) {
currentPageAssiduites--;
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
assiduiteCallBack(array);
}
@ -159,7 +167,7 @@
paginationContainerAssiduites.querySelector('.pagination_plus').addEventListener('click', () => {
if (currentPageAssiduites < totalPages) {
currentPageAssiduites++;
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites
paginationContainerAssiduites.querySelector('#paginationAssi').value = currentPageAssiduites + ""
assiduiteCallBack(array);
}
})
@ -199,8 +207,12 @@
if (assi) {
paginationContainerAssiduites.querySelector('#paginationAssi').appendChild(paginationButton)
if (i == currentPageAssiduites)
paginationContainerAssiduites.querySelector('#paginationAssi').value = i + "";
} else {
paginationContainerJustificatifs.querySelector('#paginationJusti').appendChild(paginationButton)
if (i == currentPageJustificatifs)
paginationContainerJustificatifs.querySelector('#paginationJusti').value = i + "";
}
}
updateActivePaginationButton(assi);
@ -230,8 +242,8 @@
}
function loadAll() {
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack) } catch (_) { }
try { getAllJustificatifsFromEtud(etudid, justificatifCallBack) } catch (_) { }
try { getAllAssiduitesFromEtud(etudid, assiduiteCallBack, true, true, assi_limit_annee) } catch (_) { }
try { getAllJustificatifsFromEtud(etudid, justificatifCallBack, true, assi_limit_annee) } catch (_) { }
}
function order(keyword, callback = () => { }, el, assi = true) {
@ -641,6 +653,27 @@
contextMenu.style.top = `${e.clientY - contextMenu.offsetHeight}px`;
contextMenu.style.left = `${e.clientX}px`;
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>

View File

@ -169,7 +169,7 @@
<span class="obj-content">${etat}</span>
</div>
<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>
</div>
</div>

View File

@ -18,6 +18,8 @@
const period_default = {{ periode_defaut }};
let handleMoving = false;
function createTicks() {
let i = t_start;
@ -87,72 +89,92 @@
}
function setupTimeLine(callback) {
function timelineMainEvent(event, callback) {
const func_call = callback ? callback : () => { };
timelineContainer.addEventListener("mousedown", (event) => {
const startX = event.clientX;
if (event.target === periodTimeLine) {
const startLeft = parseFloat(periodTimeLine.style.left);
const startX = (event.clientX || event.changedTouches[0].clientX);
const onMouseMove = (moveEvent) => {
const deltaX = moveEvent.clientX - startX;
const containerWidth = timelineContainer.clientWidth;
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");
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;
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) {

View File

@ -162,24 +162,35 @@ def index_html():
"""<p class="help">Pour signaler, annuler ou justifier une assiduité pour un seul étudiant,
choisissez d'abord le concerné:</p>"""
)
H.append(sco_find_etud.form_search_etud())
# if current_user.has_permission(
# Permission.ScoAbsChange
# ) and sco_preferences.get_preference("handle_billets_abs"):
# H.append(
# 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>
# """
# )
H.append(sco_find_etud.form_search_etud(dest_url="assiduites.bilan_etud"))
if current_user.has_permission(
Permission.ScoAbsChange
) and sco_preferences.get_preference("handle_billets_abs"):
H.append(
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>
"""
)
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(
render_template(
"assiduites/pages/bilan_dept.j2",
dept_id=g.scodoc_dept_id,
annee=scu.annee_scolaire(),
annees=annees_str,
),
)
H.append(html_sco_header.sco_footer())
@ -299,6 +310,8 @@ def liste_assiduites_etud():
if etud.dept_id != g.scodoc_dept_id:
abort(404, "étudiant inexistant dans ce département")
assiduite_id: int = request.args.get("assiduite_id", -1)
header: str = html_sco_header.sco_header(
page_title="Liste des assiduités",
init_qtip=True,
@ -319,6 +332,11 @@ def liste_assiduites_etud():
"assiduites/pages/liste_assiduites.j2",
sco=ScoData(etud),
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()
@ -371,6 +389,10 @@ def bilan_etud():
date_fin=date_fin,
assi_metric=assi_metric,
assi_seuil=_get_seuil(),
assi_limit_annee=sco_preferences.get_preference(
"assi_limit_annee",
dept_id=g.scodoc_dept_id,
),
),
).build()
@ -412,6 +434,12 @@ def ajout_justificatif_etud():
render_template(
"assiduites/pages/ajout_justificatif.j2",
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()

View File

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