forked from ScoDoc/ScoDoc
Assiduites : ajout préférences
This commit is contained in:
parent
c984a916dc
commit
62df621d7f
86
app/forms/main/config_assiduites.py
Normal file
86
app/forms/main/config_assiduites.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# -*- mode: python -*-
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# ScoDoc
|
||||||
|
#
|
||||||
|
# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
# Emmanuel Viennet emmanuel.viennet@viennet.net
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
"""
|
||||||
|
Formulaire configuration Module Assiduités
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import SubmitField
|
||||||
|
from wtforms.fields.simple import StringField
|
||||||
|
from wtforms.widgets import TimeInput
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class TimeField(StringField):
|
||||||
|
"""HTML5 time input."""
|
||||||
|
|
||||||
|
widget = TimeInput()
|
||||||
|
|
||||||
|
def __init__(self, label=None, validators=None, fmt="%H:%M:%S", **kwargs):
|
||||||
|
super(TimeField, self).__init__(label, validators, **kwargs)
|
||||||
|
self.fmt = fmt
|
||||||
|
self.data = None
|
||||||
|
|
||||||
|
def _value(self):
|
||||||
|
if self.raw_data:
|
||||||
|
return " ".join(self.raw_data)
|
||||||
|
if self.data and isinstance(self.data, str):
|
||||||
|
self.data = datetime.time(*map(int, self.data.split(":")))
|
||||||
|
return self.data and self.data.strftime(self.fmt) or ""
|
||||||
|
|
||||||
|
def process_formdata(self, valuelist):
|
||||||
|
if valuelist:
|
||||||
|
time_str = " ".join(valuelist)
|
||||||
|
try:
|
||||||
|
components = time_str.split(":")
|
||||||
|
hour = 0
|
||||||
|
minutes = 0
|
||||||
|
seconds = 0
|
||||||
|
if len(components) in range(2, 4):
|
||||||
|
hour = int(components[0])
|
||||||
|
minutes = int(components[1])
|
||||||
|
|
||||||
|
if len(components) == 3:
|
||||||
|
seconds = int(components[2])
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
self.data = datetime.time(hour, minutes, seconds)
|
||||||
|
except ValueError:
|
||||||
|
self.data = None
|
||||||
|
raise ValueError(self.gettext("Not a valid time string"))
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigAssiduitesForm(FlaskForm):
|
||||||
|
"Formulaire paramétrage Module Assiduités"
|
||||||
|
|
||||||
|
morning_time = TimeField("Début de la journée")
|
||||||
|
lunch_time = TimeField("Heure de midi (date pivot entre Matin et Après Midi)")
|
||||||
|
afternoon_time = TimeField("Fin de la journée")
|
||||||
|
|
||||||
|
submit = SubmitField("Valider")
|
||||||
|
cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
|
@ -8,6 +8,8 @@ from app import current_app, db, log
|
|||||||
from app.comp import bonus_spo
|
from app.comp import bonus_spo
|
||||||
from app.scodoc import sco_utils as scu
|
from app.scodoc import sco_utils as scu
|
||||||
|
|
||||||
|
from datetime import time
|
||||||
|
|
||||||
from app.scodoc.codes_cursus import (
|
from app.scodoc.codes_cursus import (
|
||||||
ABAN,
|
ABAN,
|
||||||
ABL,
|
ABL,
|
||||||
@ -94,6 +96,10 @@ class ScoDocSiteConfig(db.Model):
|
|||||||
"cas_logout_route": str,
|
"cas_logout_route": str,
|
||||||
"cas_validate_route": str,
|
"cas_validate_route": str,
|
||||||
"cas_attribute_id": str,
|
"cas_attribute_id": str,
|
||||||
|
# Assiduités
|
||||||
|
"morning_time": str,
|
||||||
|
"lunch_time": str,
|
||||||
|
"afternoon_time": str,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, name, value):
|
def __init__(self, name, value):
|
||||||
|
@ -206,6 +206,7 @@ PREF_CATEGORIES = (
|
|||||||
("misc", {"title": "Divers"}),
|
("misc", {"title": "Divers"}),
|
||||||
("apc", {"title": "BUT et Approches par Compétences"}),
|
("apc", {"title": "BUT et Approches par Compétences"}),
|
||||||
("abs", {"title": "Suivi des absences", "related": ("bul",)}),
|
("abs", {"title": "Suivi des absences", "related": ("bul",)}),
|
||||||
|
("assi", {"title": "Gestion de l'assiduité"}),
|
||||||
("portal", {"title": "Liaison avec portail (Apogée, etc)"}),
|
("portal", {"title": "Liaison avec portail (Apogée, etc)"}),
|
||||||
(
|
(
|
||||||
"pdf",
|
"pdf",
|
||||||
@ -588,6 +589,38 @@ class BasePreferences(object):
|
|||||||
"category": "abs",
|
"category": "abs",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
# Assiduités
|
||||||
|
(
|
||||||
|
"forcer_module",
|
||||||
|
{
|
||||||
|
"initvalue": 0,
|
||||||
|
"title": "Forcer la déclaration du module.",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "assi",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"forcer_present",
|
||||||
|
{
|
||||||
|
"initvalue": 0,
|
||||||
|
"title": "Forcer l'appel des présents",
|
||||||
|
"input_type": "boolcheckbox",
|
||||||
|
"labels": ["non", "oui"],
|
||||||
|
"category": "assi",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"etat_defaut",
|
||||||
|
{
|
||||||
|
"initvalue": "aucun",
|
||||||
|
"input_type": "menu",
|
||||||
|
"labels": ["aucun", "present", "retard", "absent"],
|
||||||
|
"allowed_values": ["aucun", "present", "retard", "absent"],
|
||||||
|
"title": "Définir l'état par défaut",
|
||||||
|
"category": "assi",
|
||||||
|
},
|
||||||
|
),
|
||||||
# portal
|
# portal
|
||||||
(
|
(
|
||||||
"portal_url",
|
"portal_url",
|
||||||
@ -1678,7 +1711,7 @@ class BasePreferences(object):
|
|||||||
(
|
(
|
||||||
"feuille_releve_abs_taille",
|
"feuille_releve_abs_taille",
|
||||||
{
|
{
|
||||||
"initvalue": "A3",
|
"initvalue": "A4",
|
||||||
"input_type": "menu",
|
"input_type": "menu",
|
||||||
"labels": ["A3", "A4"],
|
"labels": ["A3", "A4"],
|
||||||
"allowed_values": ["A3", "A4"],
|
"allowed_values": ["A3", "A4"],
|
||||||
|
@ -88,6 +88,20 @@ function validateSelectors() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (getModuleImplId() == null && forceModule) {
|
||||||
|
const HTML = `
|
||||||
|
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
||||||
|
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
||||||
|
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const content = document.createElement("div");
|
||||||
|
content.innerHTML = HTML;
|
||||||
|
|
||||||
|
openAlertModal("Sélection du module", content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
getAssiduitesFromEtuds(true);
|
getAssiduitesFromEtuds(true);
|
||||||
|
|
||||||
document.querySelector(".selectors").disabled = true;
|
document.querySelector(".selectors").disabled = true;
|
||||||
@ -1133,14 +1147,16 @@ function createMiniTimeline(assiduitesArray) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getJustificatifFromPeriod(
|
if (assiduité.etudid) {
|
||||||
{
|
getJustificatifFromPeriod(
|
||||||
deb: new moment.tz(assiduité.date_debut, TIMEZONE),
|
{
|
||||||
fin: new moment.tz(assiduité.date_fin, TIMEZONE),
|
deb: new moment.tz(assiduité.date_debut, TIMEZONE),
|
||||||
},
|
fin: new moment.tz(assiduité.date_fin, TIMEZONE),
|
||||||
assiduité.etudid,
|
},
|
||||||
action
|
assiduité.etudid,
|
||||||
);
|
action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
switch (assiduité.etat) {
|
switch (assiduité.etat) {
|
||||||
case "PRESENT":
|
case "PRESENT":
|
||||||
@ -1905,7 +1921,9 @@ function fastJustify(assiduite) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
getJustificatifFromPeriod(period, assiduite.etudid, action);
|
if (assiduite.etudid) {
|
||||||
|
getJustificatifFromPeriod(period, assiduite.etudid, action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function justifyAssiduite(assiduite_id, justified) {
|
function justifyAssiduite(assiduite_id, justified) {
|
||||||
|
28
app/templates/assiduites/config_assiduites.j2
Normal file
28
app/templates/assiduites/config_assiduites.j2
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends "base.j2" %}
|
||||||
|
{% import 'bootstrap/wtf.html' as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Configuration du Module d'assiduité</h1>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
|
||||||
|
<form class="form form-horizontal" method="post" enctype="multipart/form-data" role="form">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
{{ wtf.form_errors(form, hiddens="only") }}
|
||||||
|
|
||||||
|
{{ wtf.form_field(form.morning_time) }}
|
||||||
|
{{ wtf.form_field(form.lunch_time) }}
|
||||||
|
{{ wtf.form_field(form.afternoon_time) }}
|
||||||
|
<div class="form-group">
|
||||||
|
{{ wtf.form_field(form.submit) }}
|
||||||
|
{{ wtf.form_field(form.cancel) }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -49,9 +49,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="btn_group">
|
<div class="btn_group">
|
||||||
<button class="btn" onclick="setTimeLineTimes(8,18)">Journée</button>
|
<button class="btn" onclick="setTimeLineTimes({{morning}},{{afternoon}})">Journée</button>
|
||||||
<button class="btn" onclick="setTimeLineTimes(8,13)">Matin</button>
|
<button class="btn" onclick="setTimeLineTimes({{morning}},{{lunch}})">Matin</button>
|
||||||
<button class="btn" onclick="setTimeLineTimes(13,18)">Après-midi</button>
|
<button class="btn" onclick="setTimeLineTimes({{lunch}},{{afternoon}})">Après-midi</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="etud_holder">
|
<div class="etud_holder">
|
||||||
@ -94,6 +94,9 @@
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let forceModule = "{{ forcer_module }}"
|
||||||
|
forceModule = forceModule == "True" ? true : false
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,5 +73,8 @@
|
|||||||
updateDate();
|
updateDate();
|
||||||
setupDate();
|
setupDate();
|
||||||
setupTimeLine();
|
setupTimeLine();
|
||||||
|
|
||||||
|
let forceModule = "{{ forcer_module }}"
|
||||||
|
forceModule = forceModule == "True" ? true : false
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
@ -9,31 +9,60 @@
|
|||||||
|
|
||||||
const timelineContainer = document.querySelector(".timeline-container");
|
const timelineContainer = document.querySelector(".timeline-container");
|
||||||
const periodTimeLine = document.querySelector(".period");
|
const periodTimeLine = document.querySelector(".period");
|
||||||
|
const t_start = {{ t_start }}
|
||||||
|
const t_end = {{ t_end }}
|
||||||
|
|
||||||
function createTicks() {
|
function createTicks() {
|
||||||
for (let i = 8; i <= 18; i++) {
|
let i = t_start
|
||||||
|
|
||||||
|
while (i <= t_end) {
|
||||||
const hourTick = document.createElement("div");
|
const hourTick = document.createElement("div");
|
||||||
hourTick.classList.add("tick", "hour");
|
hourTick.classList.add("tick", "hour");
|
||||||
hourTick.style.left = `${((i - 8) / 10) * 100}%`;
|
hourTick.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
|
||||||
timelineContainer.appendChild(hourTick);
|
timelineContainer.appendChild(hourTick);
|
||||||
|
|
||||||
const tickLabel = document.createElement("div");
|
const tickLabel = document.createElement("div");
|
||||||
tickLabel.classList.add("tick-label");
|
tickLabel.classList.add("tick-label");
|
||||||
tickLabel.style.left = `${((i - 8) / 10) * 100}%`;
|
tickLabel.style.left = `${((i - t_start) / (t_end - t_start)) * 100}%`;
|
||||||
tickLabel.textContent = i < 10 ? `0${i}:00` : `${i}:00`;
|
tickLabel.textContent = numberToTime(i);
|
||||||
timelineContainer.appendChild(tickLabel);
|
timelineContainer.appendChild(tickLabel);
|
||||||
|
|
||||||
if (i < 18) {
|
if (i < t_end) {
|
||||||
for (let j = 1; j < 4; j++) {
|
let j = Math.floor(i + 1)
|
||||||
const quarterTick = document.createElement("div");
|
|
||||||
quarterTick.classList.add("tick", "quarter");
|
while (i < j) {
|
||||||
quarterTick.style.left = `${((i - 8 + j / 4) / 10) * 100}%`;
|
i += 0.25;
|
||||||
timelineContainer.appendChild(quarterTick);
|
|
||||||
|
if (i <= t_end) {
|
||||||
|
const quarterTick = document.createElement("div");
|
||||||
|
quarterTick.classList.add("tick", "quarter");
|
||||||
|
quarterTick.style.left = `${computePercentage(i, t_start)}%`;
|
||||||
|
timelineContainer.appendChild(quarterTick);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function numberToTime(num) {
|
||||||
|
const integer = Math.floor(num)
|
||||||
|
const decimal = (num % 1) * 60
|
||||||
|
|
||||||
|
let dec = `:${decimal}`
|
||||||
|
if (decimal < 10) {
|
||||||
|
dec = `:0${decimal}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let int = `${integer}`
|
||||||
|
if (integer < 10) {
|
||||||
|
int = `0${integer}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return int + dec
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function snapToQuarter(value) {
|
function snapToQuarter(value) {
|
||||||
return Math.round(value * 4) / 4;
|
return Math.round(value * 4) / 4;
|
||||||
}
|
}
|
||||||
@ -119,18 +148,28 @@
|
|||||||
const leftPercentage = parseFloat(periodTimeLine.style.left);
|
const leftPercentage = parseFloat(periodTimeLine.style.left);
|
||||||
const widthPercentage = parseFloat(periodTimeLine.style.width);
|
const widthPercentage = parseFloat(periodTimeLine.style.width);
|
||||||
|
|
||||||
const startHour = (leftPercentage / 100) * 10 + 8;
|
const startHour = (leftPercentage / 100) * (t_end - t_start) + t_start;
|
||||||
const endHour = ((leftPercentage + widthPercentage) / 100) * 10 + 8;
|
const endHour = ((leftPercentage + widthPercentage) / 100) * (t_end - t_start) + t_start;
|
||||||
|
|
||||||
const startValue = Math.round(startHour * 4) / 4;
|
const startValue = Math.round(startHour * 4) / 4;
|
||||||
const endValue = Math.round(endHour * 4) / 4;
|
const endValue = Math.round(endHour * 4) / 4;
|
||||||
|
|
||||||
return [startValue, endValue]
|
const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)]
|
||||||
|
|
||||||
|
if (computedValues[0] > t_end || computedValues[1] < t_start) {
|
||||||
|
return [8, 10]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (computedValues[1] - computedValues[0] <= 0.25 && computedValues[1] < t_end - 0.25) {
|
||||||
|
computedValues[1] += 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedValues
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPeriodValues(deb, fin) {
|
function setPeriodValues(deb, fin) {
|
||||||
let leftPercentage = (deb - 8) / 10 * 100
|
let leftPercentage = (deb - t_start) / (t_end - t_start) * 100
|
||||||
let widthPercentage = (fin - deb) / 10 * 100
|
let widthPercentage = (fin - deb) / (t_end - t_start) * 100
|
||||||
periodTimeLine.style.left = `${leftPercentage}%`
|
periodTimeLine.style.left = `${leftPercentage}%`
|
||||||
periodTimeLine.style.width = `${widthPercentage}%`
|
periodTimeLine.style.width = `${widthPercentage}%`
|
||||||
|
|
||||||
@ -140,12 +179,13 @@
|
|||||||
|
|
||||||
function snapHandlesToQuarters() {
|
function snapHandlesToQuarters() {
|
||||||
const periodValues = getPeriodValues();
|
const periodValues = getPeriodValues();
|
||||||
let lef = Math.min((periodValues[0] - 8) * 10, 97.5)
|
let lef = Math.min(computePercentage(periodValues[0], t_start), computePercentage(t_end, 0.25))
|
||||||
if (lef < 0) {
|
if (lef < 0) {
|
||||||
lef = 0;
|
lef = 0;
|
||||||
}
|
}
|
||||||
const left = `${lef}%`
|
const left = `${lef}%`
|
||||||
let wid = Math.max((periodValues[1] - periodValues[0]) * 10, 2.5)
|
|
||||||
|
let wid = Math.max(computePercentage(periodValues[1], periodValues[0]), computePercentage(0.25, 0))
|
||||||
if (wid > 100) {
|
if (wid > 100) {
|
||||||
wid = 100;
|
wid = 100;
|
||||||
}
|
}
|
||||||
@ -154,7 +194,12 @@
|
|||||||
periodTimeLine.style.width = width;
|
periodTimeLine.style.width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computePercentage(a, b) {
|
||||||
|
return ((a - b) / (t_end - t_start)) * 100
|
||||||
|
}
|
||||||
|
|
||||||
createTicks();
|
createTicks();
|
||||||
|
setPeriodValues(8, 9)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
|
@ -52,20 +52,25 @@
|
|||||||
<p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">configuration des codes de décision</a>
|
<p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">configuration des codes de décision</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2>Assiduités</h2>
|
||||||
|
<p><a class="stdlink" href="{{url_for('scodoc.config_assiduites')}}">configuration du module d'assiduités</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
<h2>Utilisateurs et CAS</h2>
|
<h2>Utilisateurs et CAS</h2>
|
||||||
<section>
|
<section>
|
||||||
<div>
|
<div>
|
||||||
🏰 <a class="stdlink" href="{{url_for('scodoc.config_cas')}}">Configuration du service CAS</a>
|
🏰 <a class="stdlink" href="{{url_for('scodoc.config_cas')}}">Configuration du service CAS</a>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 16px;">
|
<div style="margin-top: 16px;">
|
||||||
🧑🏾🤝🧑🏼 <a class="stdlink" href="{{ url_for('auth.cas_users_import_config') }}">
|
🧑🏾🤝🧑🏼 <a class="stdlink" href="{{ url_for('auth.cas_users_import_config') }}">
|
||||||
Configurer les comptes utilisateurs pour le CAS</a>
|
Configurer les comptes utilisateurs pour le CAS</a>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 16px;">
|
<div style="margin-top: 16px;">
|
||||||
🛟 <a class="stdlink" href="{{url_for('auth.reset_standard_roles_permissions')}}">Remettre
|
🛟 <a class="stdlink" href="{{url_for('auth.reset_standard_roles_permissions')}}">Remettre
|
||||||
les permissions des rôles standards à leurs valeurs par défaut</a>
|
les permissions des rôles standards à leurs valeurs par défaut</a>
|
||||||
(efface les modifications apportées aux rôles)
|
(efface les modifications apportées aux rôles)
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from app.decorators import (
|
|||||||
scodoc,
|
scodoc,
|
||||||
permission_required,
|
permission_required,
|
||||||
)
|
)
|
||||||
from app.models import FormSemestre, Identite
|
from app.models import FormSemestre, Identite, ScoDocSiteConfig
|
||||||
from app.views import assiduites_bp as bp
|
from app.views import assiduites_bp as bp
|
||||||
from app.views import ScoData
|
from app.views import ScoData
|
||||||
|
|
||||||
@ -190,6 +190,12 @@ def signal_assiduites_etud():
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Gestion des horaires (journée, matin, soir)
|
||||||
|
|
||||||
|
morning = get_time("assi_morning_time", "08:00:00")
|
||||||
|
lunch = get_time("assi_lunch_time", "13:00:00")
|
||||||
|
afternoon = get_time("assi_afternoon_time", "18:00:00")
|
||||||
|
|
||||||
return HTMLBuilder(
|
return HTMLBuilder(
|
||||||
header,
|
header,
|
||||||
render_template("assiduites/minitimeline.j2"),
|
render_template("assiduites/minitimeline.j2"),
|
||||||
@ -197,10 +203,27 @@ def signal_assiduites_etud():
|
|||||||
"assiduites/signal_assiduites_etud.j2",
|
"assiduites/signal_assiduites_etud.j2",
|
||||||
sco=ScoData(etud),
|
sco=ScoData(etud),
|
||||||
date=datetime.date.today().isoformat(),
|
date=datetime.date.today().isoformat(),
|
||||||
|
morning=morning,
|
||||||
|
lunch=lunch,
|
||||||
|
afternoon=afternoon,
|
||||||
|
forcer_module=sco_preferences.get_preference(
|
||||||
|
"forcer_module", dept_id=g.scodoc_dept_id
|
||||||
|
),
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
|
|
||||||
|
|
||||||
|
def _str_to_num(string: str):
|
||||||
|
parts = [*map(float, string.split(":"))]
|
||||||
|
hour = parts[0]
|
||||||
|
minutes = round(parts[1] / 60 * 4) / 4
|
||||||
|
return hour + minutes
|
||||||
|
|
||||||
|
|
||||||
|
def get_time(label: str, default: str):
|
||||||
|
return _str_to_num(ScoDocSiteConfig.get(label, default))
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ListeAssiduitesEtud")
|
@bp.route("/ListeAssiduitesEtud")
|
||||||
@scodoc
|
@scodoc
|
||||||
@permission_required(Permission.ScoAbsChange)
|
@permission_required(Permission.ScoAbsChange)
|
||||||
@ -373,6 +396,11 @@ def signal_assiduites_group():
|
|||||||
timeline=_timeline(),
|
timeline=_timeline(),
|
||||||
formsemestre_date_debut=str(formsemestre.date_debut),
|
formsemestre_date_debut=str(formsemestre.date_debut),
|
||||||
formsemestre_date_fin=str(formsemestre.date_fin),
|
formsemestre_date_fin=str(formsemestre.date_fin),
|
||||||
|
forcer_module=sco_preferences.get_preference(
|
||||||
|
"forcer_module",
|
||||||
|
formsemestre_id=formsemestre_id,
|
||||||
|
dept_id=g.scodoc_dept_id,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
html_sco_header.sco_footer(),
|
html_sco_header.sco_footer(),
|
||||||
).build()
|
).build()
|
||||||
@ -416,4 +444,8 @@ def _module_selector(
|
|||||||
|
|
||||||
|
|
||||||
def _timeline() -> HTMLElement:
|
def _timeline() -> HTMLElement:
|
||||||
return render_template("assiduites/timeline.j2")
|
return render_template(
|
||||||
|
"assiduites/timeline.j2",
|
||||||
|
t_start=get_time("assi_morning_time", "08:00:00"),
|
||||||
|
t_end=get_time("assi_afternoon_time", "18:00:00"),
|
||||||
|
)
|
||||||
|
@ -64,6 +64,7 @@ from app.forms.main import config_logos, config_main
|
|||||||
from app.forms.main.create_dept import CreateDeptForm
|
from app.forms.main.create_dept import CreateDeptForm
|
||||||
from app.forms.main.config_apo import CodesDecisionsForm
|
from app.forms.main.config_apo import CodesDecisionsForm
|
||||||
from app.forms.main.config_cas import ConfigCASForm
|
from app.forms.main.config_cas import ConfigCASForm
|
||||||
|
from app.forms.main.config_assiduites import ConfigAssiduitesForm
|
||||||
from app import models
|
from app import models
|
||||||
from app.models import Departement, Identite
|
from app.models import Departement, Identite
|
||||||
from app.models import departements
|
from app.models import departements
|
||||||
@ -188,6 +189,39 @@ def config_cas():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/ScoDoc/config_assiduites", methods=["GET", "POST"])
|
||||||
|
@admin_required
|
||||||
|
def config_assiduites():
|
||||||
|
"""Form config Assiduites"""
|
||||||
|
form = ConfigAssiduitesForm()
|
||||||
|
if request.method == "POST" and form.cancel.data: # cancel button
|
||||||
|
return redirect(url_for("scodoc.index"))
|
||||||
|
if form.validate_on_submit():
|
||||||
|
if ScoDocSiteConfig.set("assi_morning_time", form.data["morning_time"]):
|
||||||
|
flash("Heure du début de la journée enregistrée")
|
||||||
|
if ScoDocSiteConfig.set("assi_lunch_time", form.data["lunch_time"]):
|
||||||
|
flash("Heure de midi enregistrée")
|
||||||
|
if ScoDocSiteConfig.set("assi_afternoon_time", form.data["afternoon_time"]):
|
||||||
|
flash("Heure de fin de la journée enregistrée")
|
||||||
|
return redirect(url_for("scodoc.configuration"))
|
||||||
|
|
||||||
|
elif request.method == "GET":
|
||||||
|
form.morning_time.data = ScoDocSiteConfig.get(
|
||||||
|
"assi_morning_time", datetime.time(8, 0, 0)
|
||||||
|
)
|
||||||
|
form.lunch_time.data = ScoDocSiteConfig.get(
|
||||||
|
"assi_lunch_time", datetime.time(13, 0, 0)
|
||||||
|
)
|
||||||
|
form.afternoon_time.data = ScoDocSiteConfig.get(
|
||||||
|
"assi_afternoon_time", datetime.time(18, 0, 0)
|
||||||
|
)
|
||||||
|
return render_template(
|
||||||
|
"assiduites/config_assiduites.j2",
|
||||||
|
form=form,
|
||||||
|
title="Configuration du module Assiduités",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])
|
@bp.route("/ScoDoc/config_codes_decisions", methods=["GET", "POST"])
|
||||||
@admin_required
|
@admin_required
|
||||||
def config_codes_decisions():
|
def config_codes_decisions():
|
||||||
|
@ -655,21 +655,21 @@ def profile(host, port, length, profile_dir):
|
|||||||
"-m",
|
"-m",
|
||||||
"--morning",
|
"--morning",
|
||||||
help="Spécifie l'heure de début des cours format `hh:mm`",
|
help="Spécifie l'heure de début des cours format `hh:mm`",
|
||||||
default="08h00",
|
default="Heure configurée dans la configuration générale / 08:00 sinon",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-n",
|
"-n",
|
||||||
"--noon",
|
"--noon",
|
||||||
help="Spécifie l'heure de fin du matin (et donc début de l'après-midi) format `hh:mm`",
|
help="Spécifie l'heure de fin du matin (et donc début de l'après-midi) format `hh:mm`",
|
||||||
default="12h00",
|
default="Heure configurée dans la configuration générale / 13:00 sinon",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
)
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"-e",
|
"-e",
|
||||||
"--evening",
|
"--evening",
|
||||||
help="Spécifie l'heure de fin des cours format `hh:mm`",
|
help="Spécifie l'heure de fin des cours format `hh:mm`",
|
||||||
default="18h00",
|
default="Heure configurée dans la configuration générale / 18:00 sinon",
|
||||||
show_default=True,
|
show_default=True,
|
||||||
)
|
)
|
||||||
@with_appcontext
|
@with_appcontext
|
||||||
|
@ -16,6 +16,9 @@ from app.models import (
|
|||||||
Justificatif,
|
Justificatif,
|
||||||
ModuleImplInscription,
|
ModuleImplInscription,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from app.models.config import ScoDocSiteConfig
|
||||||
|
|
||||||
from app.models.assiduites import (
|
from app.models.assiduites import (
|
||||||
compute_assiduites_justified,
|
compute_assiduites_justified,
|
||||||
)
|
)
|
||||||
@ -181,7 +184,6 @@ class _Statistics:
|
|||||||
"""Comptage des statistiques"""
|
"""Comptage des statistiques"""
|
||||||
stats: dict = {"total": self.object["total"]}
|
stats: dict = {"total": self.object["total"]}
|
||||||
for year, item in self.object.items():
|
for year, item in self.object.items():
|
||||||
|
|
||||||
if year == "total":
|
if year == "total":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -226,21 +228,21 @@ def migrate_abs_to_assiduites(
|
|||||||
_glob.DEBUG = debug
|
_glob.DEBUG = debug
|
||||||
|
|
||||||
if morning is None:
|
if morning is None:
|
||||||
_glob.MORNING = time(8, 0)
|
_glob.MORNING = ScoDocSiteConfig.get("assi_morning_time", time(8, 0))
|
||||||
else:
|
else:
|
||||||
morning: list[str] = morning.split("h")
|
morning: list[str] = morning.split(":")
|
||||||
_glob.MORNING = time(int(morning[0]), int(morning[1]))
|
_glob.MORNING = time(int(morning[0]), int(morning[1]))
|
||||||
|
|
||||||
if noon is None:
|
if noon is None:
|
||||||
_glob.NOON = time(12, 0)
|
_glob.NOON = ScoDocSiteConfig.get("assi_lunch_time", time(13, 0))
|
||||||
else:
|
else:
|
||||||
noon: list[str] = noon.split("h")
|
noon: list[str] = noon.split(":")
|
||||||
_glob.NOON = time(int(noon[0]), int(noon[1]))
|
_glob.NOON = time(int(noon[0]), int(noon[1]))
|
||||||
|
|
||||||
if evening is None:
|
if evening is None:
|
||||||
_glob.EVENING = time(18, 0)
|
_glob.EVENING = ScoDocSiteConfig.get("assi_afternoon_time", time(18, 0))
|
||||||
else:
|
else:
|
||||||
evening: list[str] = evening.split("h")
|
evening: list[str] = evening.split(":")
|
||||||
_glob.EVENING = time(int(evening[0]), int(evening[1]))
|
_glob.EVENING = time(int(evening[0]), int(evening[1]))
|
||||||
|
|
||||||
if dept is None:
|
if dept is None:
|
||||||
@ -379,7 +381,6 @@ def migrate_dept(dept_name: str, stats: _Statistics, time_elapsed: Profiler):
|
|||||||
|
|
||||||
|
|
||||||
def _from_abs_to_assiduite_justificatif(_abs: Absence):
|
def _from_abs_to_assiduite_justificatif(_abs: Absence):
|
||||||
|
|
||||||
if _abs.etudid not in _glob.CURRENT_ETU:
|
if _abs.etudid not in _glob.CURRENT_ETU:
|
||||||
etud: Identite = Identite.query.filter_by(id=_abs.etudid).first()
|
etud: Identite = Identite.query.filter_by(id=_abs.etudid).first()
|
||||||
if etud is None:
|
if etud is None:
|
||||||
|
Loading…
Reference in New Issue
Block a user