diff --git a/app/forms/main/config_assiduites.py b/app/forms/main/config_assiduites.py new file mode 100644 index 00000000..26126f2e --- /dev/null +++ b/app/forms/main/config_assiduites.py @@ -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}) diff --git a/app/models/config.py b/app/models/config.py index 71b462bc..b989781c 100644 --- a/app/models/config.py +++ b/app/models/config.py @@ -8,6 +8,8 @@ from app import current_app, db, log from app.comp import bonus_spo from app.scodoc import sco_utils as scu +from datetime import time + from app.scodoc.codes_cursus import ( ABAN, ABL, @@ -94,6 +96,10 @@ class ScoDocSiteConfig(db.Model): "cas_logout_route": str, "cas_validate_route": str, "cas_attribute_id": str, + # Assiduités + "morning_time": str, + "lunch_time": str, + "afternoon_time": str, } def __init__(self, name, value): diff --git a/app/scodoc/sco_preferences.py b/app/scodoc/sco_preferences.py index b77ee34b..4131e7f7 100644 --- a/app/scodoc/sco_preferences.py +++ b/app/scodoc/sco_preferences.py @@ -206,6 +206,7 @@ PREF_CATEGORIES = ( ("misc", {"title": "Divers"}), ("apc", {"title": "BUT et Approches par Compétences"}), ("abs", {"title": "Suivi des absences", "related": ("bul",)}), + ("assi", {"title": "Gestion de l'assiduité"}), ("portal", {"title": "Liaison avec portail (Apogée, etc)"}), ( "pdf", @@ -588,6 +589,38 @@ class BasePreferences(object): "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_url", @@ -1678,7 +1711,7 @@ class BasePreferences(object): ( "feuille_releve_abs_taille", { - "initvalue": "A3", + "initvalue": "A4", "input_type": "menu", "labels": ["A3", "A4"], "allowed_values": ["A3", "A4"], diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index 94efe5f9..bf3d821b 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -88,6 +88,20 @@ function validateSelectors() { ); }); + if (getModuleImplId() == null && forceModule) { + const HTML = ` +
Attention, le module doit obligatoirement être renseigné.
+Cela vient de la configuration du semestre ou plus largement du département.
+Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.
+ `; + + const content = document.createElement("div"); + content.innerHTML = HTML; + + openAlertModal("Sélection du module", content); + return; + } + getAssiduitesFromEtuds(true); document.querySelector(".selectors").disabled = true; @@ -1133,14 +1147,16 @@ function createMiniTimeline(assiduitesArray) { } }; - getJustificatifFromPeriod( - { - deb: new moment.tz(assiduité.date_debut, TIMEZONE), - fin: new moment.tz(assiduité.date_fin, TIMEZONE), - }, - assiduité.etudid, - action - ); + if (assiduité.etudid) { + getJustificatifFromPeriod( + { + deb: new moment.tz(assiduité.date_debut, TIMEZONE), + fin: new moment.tz(assiduité.date_fin, TIMEZONE), + }, + assiduité.etudid, + action + ); + } switch (assiduité.etat) { 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) { diff --git a/app/templates/assiduites/config_assiduites.j2 b/app/templates/assiduites/config_assiduites.j2 new file mode 100644 index 00000000..92d9dd24 --- /dev/null +++ b/app/templates/assiduites/config_assiduites.j2 @@ -0,0 +1,28 @@ +{% extends "base.j2" %} +{% import 'bootstrap/wtf.html' as wtf %} + +{% block app_content %} +