From 939371cff912f3f70a7c44401ae6b46b9663543f Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 21 Nov 2023 12:19:35 +0100 Subject: [PATCH 1/9] complete message aide --- app/scodoc/sco_groups_edit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scodoc/sco_groups_edit.py b/app/scodoc/sco_groups_edit.py index 6c6cfd422..bb0429eff 100644 --- a/app/scodoc/sco_groups_edit.py +++ b/app/scodoc/sco_groups_edit.py @@ -101,7 +101,8 @@ def group_rename(group_id): "allow_null": True, "explanation": """optionnel : identifiant du groupe dans le logiciel d'emploi du temps, pour le cas où les noms de groupes ne seraient pas - les mêmes dans ScoDoc et dans l'emploi du temps (si plusieurs ids, + les mêmes dans ScoDoc et dans l'emploi du temps (si plusieurs ids de + groupes EDT doivent correspondre au même groupe ScoDoc, les séparer par des virgules).""", }, ), From 4d3cbf7e7561fb1828190e39b1e0ec354fd2789b Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Tue, 21 Nov 2023 22:28:50 +0100 Subject: [PATCH 2/9] =?UTF-8?q?API:=20enrichit=20cr=C3=A9ation/=C3=A9ditio?= =?UTF-8?q?n=20User?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/users.py | 49 +++++++++++++++++-------- app/auth/models.py | 55 ++++++++++++++++------------ app/models/__init__.py | 6 ++- app/scodoc/sco_users.py | 5 ++- app/static/css/scodoc.css | 8 ++-- app/templates/auth/user_info_page.j2 | 1 + app/views/users.py | 11 ++++++ tests/api/test_api_users.py | 4 +- tests/unit/test_users.py | 28 ++++++++++++++ 9 files changed, 119 insertions(+), 48 deletions(-) diff --git a/app/api/users.py b/app/api/users.py index 4fe895107..3bbc96d33 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -7,7 +7,7 @@ """ ScoDoc 9 API : accès aux utilisateurs """ - +import datetime from flask import g, request from flask_json import as_json @@ -85,6 +85,20 @@ def users_info_query(): return [user.to_dict() for user in query] +def _is_allowed_user_edit(args: dict) -> tuple[bool, str]: + "Vrai si on peut" + if "cas_id" in args and not current_user.has_permission( + Permission.UsersChangeCASId + ): + return False, "non autorise a changer cas_id" + + if not current_user.is_administrator(): + for field in ("cas_allow_login", "cas_allow_scodoc_login"): + if field in args: + return False, f"non autorise a changer {field}" + return True, "" + + @bp.route("/user/create", methods=["POST"]) @api_web_bp.route("/user/create", methods=["POST"]) @login_required @@ -95,21 +109,22 @@ def user_create(): """Création d'un utilisateur The request content type should be "application/json": { - "user_name": str, + "active":bool (default True), "dept": str or null, "nom": str, "prenom": str, - "active":bool (default True) + "user_name": str, + ... } """ - data = request.get_json(force=True) # may raise 400 Bad Request - user_name = data.get("user_name") + args = request.get_json(force=True) # may raise 400 Bad Request + user_name = args.get("user_name") if not user_name: return json_error(404, "empty user_name") user = User.query.filter_by(user_name=user_name).first() if user: return json_error(404, f"user_create: user {user} already exists\n") - dept = data.get("dept") + dept = args.get("dept") if dept == "@all": dept = None allowed_depts = current_user.get_depts_with_permission(Permission.UsersAdmin) @@ -119,10 +134,12 @@ def user_create(): Departement.query.filter_by(acronym=dept).first() is None ): return json_error(404, "user_create: departement inexistant") - nom = data.get("nom") - prenom = data.get("prenom") - active = scu.to_bool(data.get("active", True)) - user = User(user_name=user_name, active=active, dept=dept, nom=nom, prenom=prenom) + args["dept"] = dept + ok, msg = _is_allowed_user_edit(args) + if not ok: + return json_error(403, f"user_create: {msg}") + user = User() + user.from_dict(args, new_user=True) db.session.add(user) db.session.commit() return user.to_dict() @@ -142,13 +159,14 @@ def user_edit(uid: int): "nom": str, "prenom": str, "active":bool + ... } """ - data = request.get_json(force=True) # may raise 400 Bad Request + args = request.get_json(force=True) # may raise 400 Bad Request user: User = User.query.get_or_404(uid) # L'utilisateur doit avoir le droit dans le département de départ et celui d'arrivée orig_dept = user.dept - dest_dept = data.get("dept", False) + dest_dept = args.get("dept", False) if dest_dept is not False: if dest_dept == "@all": dest_dept = None @@ -164,10 +182,11 @@ def user_edit(uid: int): return json_error(404, "user_edit: departement inexistant") user.dept = dest_dept - user.nom = data.get("nom", user.nom) - user.prenom = data.get("prenom", user.prenom) - user.active = scu.to_bool(data.get("active", user.active)) + ok, msg = _is_allowed_user_edit(args) + if not ok: + return json_error(403, f"user_edit: {msg}") + user.from_dict(args) db.session.add(user) db.session.commit() return user.to_dict() diff --git a/app/auth/models.py b/app/auth/models.py index dcf762a2d..38bad0974 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -21,7 +21,7 @@ from werkzeug.security import generate_password_hash, check_password_hash import jwt from app import db, email, log, login -from app.models import Departement +from app.models import Departement, ScoDocModel from app.models import SHORT_STR_LEN, USERNAME_STR_LEN from app.models.config import ScoDocSiteConfig from app.scodoc.sco_exceptions import ScoValueError @@ -59,7 +59,7 @@ def invalid_user_name(user_name: str) -> bool: ) -class User(UserMixin, db.Model): +class User(UserMixin, db.Model, ScoDocModel): """ScoDoc users, handled by Flask / SQLAlchemy""" id = db.Column(db.Integer, primary_key=True) @@ -121,7 +121,7 @@ class User(UserMixin, db.Model): # check login: if kwargs.get("user_name") and invalid_user_name(kwargs["user_name"]): raise ValueError(f"invalid user_name: {kwargs['user_name']}") - super(User, self).__init__(**kwargs) + super().__init__(**kwargs) # Ajoute roles: if ( not self.roles @@ -251,12 +251,13 @@ class User(UserMixin, db.Model): "cas_last_login": self.cas_last_login.isoformat() + "Z" if self.cas_last_login else None, + "edt_id": self.edt_id, "status_txt": "actif" if self.active else "fermé", "last_seen": self.last_seen.isoformat() + "Z" if self.last_seen else None, - "nom": (self.nom or ""), # sco8 - "prenom": (self.prenom or ""), # sco8 + "nom": self.nom or "", + "prenom": self.prenom or "", "roles_string": self.get_roles_string(), # eg "Ens_RT, Ens_Info" - "user_name": self.user_name, # sco8 + "user_name": self.user_name, # Les champs calculés: "nom_fmt": self.get_nom_fmt(), "prenom_fmt": self.get_prenom_fmt(), @@ -270,28 +271,34 @@ class User(UserMixin, db.Model): data["email_institutionnel"] = self.email_institutionnel or "" return data + @classmethod + def convert_dict_fields(cls, args: dict) -> dict: + """Convert fields in the given dict. No other side effect. + args: dict with args in application. + returns: dict to store in model's db. + Convert boolean values to bools. + """ + args_dict = args + if "date_expiration" in args: + date_expiration = args.get("date_expiration") + if isinstance(date_expiration, str): + args["date_expiration"] = ( + datetime.datetime.fromisoformat(date_expiration) + if date_expiration + else None + ) + + for field in ("active", "cas_allow_login", "cas_allow_scodoc_login"): + if field in args: + args_dict[field] = scu.to_bool(args.get(field)) + return args_dict + def from_dict(self, data: dict, new_user=False): """Set users' attributes from given dict values. Roles must be encoded as "roles_string", like "Ens_RT, Secr_CJ" + Does not check permissions here. """ - for field in [ - "nom", - "prenom", - "dept", - "active", - "email", - "email_institutionnel", - "date_expiration", - "cas_id", - ]: - if field in data: - setattr(self, field, data[field] or None) - # required boolean fields - for field in [ - "cas_allow_login", - "cas_allow_scodoc_login", - ]: - setattr(self, field, scu.to_bool(data.get(field, False))) + super().from_dict(data, excluded=("user_name", "roles_string")) if new_user: if "user_name" in data: diff --git a/app/models/__init__.py b/app/models/__init__.py index c4e04bd6b..76967ed42 100644 --- a/app/models/__init__.py +++ b/app/models/__init__.py @@ -78,9 +78,11 @@ class ScoDocModel: # virtual, by default, do nothing return args - def from_dict(self, args: dict): + def from_dict(self, args: dict, excluded: set[str] = None): "Update object's fields given in dict. Add to session but don't commit." - args_dict = self.convert_dict_fields(self.filter_model_attributes(args)) + args_dict = self.convert_dict_fields( + self.filter_model_attributes(args, excluded=excluded) + ) for key, value in args_dict.items(): if hasattr(self, key): setattr(self, key, value) diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py index a102700a5..fad28632e 100644 --- a/app/scodoc/sco_users.py +++ b/app/scodoc/sco_users.py @@ -102,7 +102,7 @@ def index_html( {menu_roles} - + """ ) @@ -204,7 +204,7 @@ def list_users( "cas_allow_scodoc_login", "cas_last_login", ] - columns_ids.append("email_institutionnel") + columns_ids += ["email_institutionnel", "edt_id"] title = "Utilisateurs définis dans ScoDoc" tab = GenTable( @@ -227,6 +227,7 @@ def list_users( "cas_allow_login": "CAS autorisé", "cas_allow_scodoc_login": "Cnx sans CAS", "cas_last_login": "Dernier login CAS", + "edt_id": "Identifiant emploi du temps", }, caption=title, page_title="title", diff --git a/app/static/css/scodoc.css b/app/static/css/scodoc.css index 0f9072c81..f96efa8b4 100644 --- a/app/static/css/scodoc.css +++ b/app/static/css/scodoc.css @@ -4347,6 +4347,10 @@ table.dataTable td.group { background: #fff; } +#zonePartitions .edt_id { + color: rgb(85, 255, 24); +} + /* ------------- Nouveau tableau recap ------------ */ div.table_recap { margin-top: 6px; @@ -4856,7 +4860,3 @@ div.cas_etat_certif_ssl { font-style: italic; color: rgb(231, 0, 0); } - -.edt_id { - color: rgb(85, 255, 24); -} diff --git a/app/templates/auth/user_info_page.j2 b/app/templates/auth/user_info_page.j2 index 8a6cb7f6e..e21da1650 100644 --- a/app/templates/auth/user_info_page.j2 +++ b/app/templates/auth/user_info_page.j2 @@ -18,6 +18,7 @@ Prénom : {{user.prenom or ""}}
Mail : {{user.email}}
Mail institutionnel: {{user.email_institutionnel or ""}}
+ Identifiant EDT: {{user.edt_id or ""}}
Rôles : {{user.get_roles_string()}}
Dept : {{user.dept or ""}}
{% if user.passwd_temp or user.password_scodoc7 %} diff --git a/app/views/users.py b/app/views/users.py index 3d07b05a3..3f360c458 100644 --- a/app/views/users.py +++ b/app/views/users.py @@ -450,6 +450,17 @@ def create_user_form(user_name=None, edit=0, all_roles=True): "readonly": edit_only_roles, }, ), + ( + "edt_id", + { + "title": "Identifiant sur l'emploi du temps", + "input_type": "text", + "explanation": """id du compte utilisateur sur l'emploi du temps + ou l'annuaire de l'établissement (par défaut, l'e-mail institutionnel )""", + "size": 36, + "allow_null": True, + }, + ), ] if not edit: # options création utilisateur descr += [ diff --git a/tests/api/test_api_users.py b/tests/api/test_api_users.py index 1421a29cf..778565539 100644 --- a/tests/api/test_api_users.py +++ b/tests/api/test_api_users.py @@ -88,15 +88,17 @@ def test_edit_users(api_admin_headers): # Change le dept et rend inactif user = POST_JSON( f"/user/{user['id']}/edit", - {"active": False, "dept": "TAPI"}, + {"active": False, "dept": "TAPI", "edt_id": "GGG"}, headers=admin_h, ) assert user["dept"] == "TAPI" assert user["active"] is False + assert user["edt_id"] == "GGG" user = GET(f"/user/{user['id']}", headers=admin_h) assert user["nom"] == "Toto" assert user["dept"] == "TAPI" assert user["active"] is False + assert user["edt_id"] == "GGG" def test_roles(api_admin_headers): diff --git a/tests/unit/test_users.py b/tests/unit/test_users.py index 789ebc5ca..8f4d8a84f 100644 --- a/tests/unit/test_users.py +++ b/tests/unit/test_users.py @@ -123,3 +123,31 @@ def test_create_delete(test_client): db.session.commit() ul = User.query.filter_by(prenom="Pierre").all() assert len(ul) == 1 + + +def test_edit(test_client): + "test edition object utlisateur" + args = { + "user_name": "Tonari", + "prenom": "No Totoro", + "edt_id": "totorito", + "cas_allow_login": 1, # boolean + "irrelevant": "..", # intentionnellement en dehors des attributs + } + u = User() + u.from_dict(args) + db.session.add(u) + db.session.commit() + db.session.refresh(u) + assert u.edt_id == "totorito" + assert u.nom is None + assert u.cas_allow_login is True + d = u.to_dict() + assert d["nom"] == "" + args["cas_allow_login"] = 0 + u.from_dict(args) + db.session.commit() + db.session.refresh(u) + assert u.cas_allow_login is False + db.session.delete(u) + db.session.commit() From 54906c1bde6f2233171fa1aecdacaef7073435b0 Mon Sep 17 00:00:00 2001 From: Iziram Date: Wed, 22 Nov 2023 09:13:00 +0100 Subject: [PATCH 3/9] =?UTF-8?q?Assiduites=20:=20mises=20=C3=A0=20jour=20co?= =?UTF-8?q?uleurs=20listes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/static/css/assiduites.css | 6 ++++-- app/templates/assiduites/widgets/tableau_assi.j2 | 1 + app/templates/assiduites/widgets/tableau_base.j2 | 13 +++++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/app/static/css/assiduites.css b/app/static/css/assiduites.css index fe429ecfc..ed5daa259 100644 --- a/app/static/css/assiduites.css +++ b/app/static/css/assiduites.css @@ -1,15 +1,17 @@ :root { --color-present: #6bdb83; --color-absent: #e62a11; + --color-absent-clair: #F25D4A; --color-retard: #f0c865; --color-justi: #7059FF; + --color-justi-clair: #6885E3; --color-justi-invalide: #a84476; --color-nonwork: #badfff; --color-absent-justi: #e65ab7; --color-retard-justi: #ffef7a; - --color-error: #FF0000; + --color-error: #e62a11; --color-warning: #eec660; --color-information: #658ef0; @@ -21,7 +23,7 @@ --color-defaut: #FFF; --color-defaut-dark: #444; - + --color-default-text: #1F1F1F; --motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px); diff --git a/app/templates/assiduites/widgets/tableau_assi.j2 b/app/templates/assiduites/widgets/tableau_assi.j2 index 1b72ebbf1..92e6aef81 100644 --- a/app/templates/assiduites/widgets/tableau_assi.j2 +++ b/app/templates/assiduites/widgets/tableau_assi.j2 @@ -88,6 +88,7 @@ td.textContent = getModuleImpl(assiduite); } else if (k.indexOf('est_just') != -1) { td.textContent = assiduite[k] ? "Oui" : "Non" + if (assiduite[k]) row.classList.add("est_just") } else if (k.indexOf('etudid') != -1) { const e = getEtudiant(assiduite.etudid); diff --git a/app/templates/assiduites/widgets/tableau_base.j2 b/app/templates/assiduites/widgets/tableau_base.j2 index 2eb5e34e5..71580a14e 100644 --- a/app/templates/assiduites/widgets/tableau_base.j2 +++ b/app/templates/assiduites/widgets/tableau_base.j2 @@ -456,6 +456,7 @@ td { border: 1px solid #dddddd; padding: 8px; + color: var(--color-default-text); } th { @@ -498,17 +499,25 @@ .l-absent, .l-invalid { - background-color: var(--color-absent); + background-color: var(--color-absent-clair); } .l-valid { - background-color: var(--color-primary); + background-color: var(--color-justi-clair); } .l-retard { background-color: var(--color-retard); } + .l-absent.est_just { + background-color: var(--color-absent-justi); + } + + .l-retard.est_just { + background-color: var(--color-retard-justi); + } + /* Ajoutez des styles pour le conteneur de pagination et les boutons */ .pagination-container { display: flex; From 33c9a606b0cd50c553399c67b56aea8e7f44fc7a Mon Sep 17 00:00:00 2001 From: Iziram Date: Wed, 22 Nov 2023 15:31:35 +0100 Subject: [PATCH 4/9] Assiduites : SaisieAssiduiteEtud rework --- app/static/js/assiduites.js | 83 +++++++ .../assiduites/pages/ajout_assiduites.j2 | 234 ++++++++++++++++++ .../assiduites/pages/ajout_justificatif.j2 | 23 +- app/templates/assiduites/pages/calendrier.j2 | 4 +- .../widgets/moduleimpl_dynamic_selector.j2 | 17 +- app/views/assiduites.py | 45 ++-- 6 files changed, 378 insertions(+), 28 deletions(-) create mode 100644 app/templates/assiduites/pages/ajout_assiduites.j2 diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index aadd04cae..8b4df342c 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -953,6 +953,89 @@ function createAssiduite(etat, etudid) { ); return !with_errors; } +/** + * Création d'une assiduité pour un étudiant + * @param {String} etat l'état de l'étudiant + * @param {Number | String} etudid l'identifiant de l'étudiant + * + * TODO : Rendre asynchrone + */ +function createAssiduiteComplete(assiduite, etudid) { + if (!hasModuleImpl(assiduite) && window.forceModule) { + const html = ` +

Aucun module n'a été spécifié

+ `; + const div = document.createElement("div"); + div.innerHTML = html; + openAlertModal("Erreur Module", div); + return false; + } + + const path = getUrl() + `/api/assiduite/${etudid}/create`; + + let with_errors = false; + + sync_post( + path, + [assiduite], + (data, status) => { + //success + if (data.success.length > 0) { + let obj = data.success["0"].message.assiduite_id; + } + if (data.errors.length > 0) { + console.error(data.errors["0"].message); + if (data.errors["0"].message == "Module non renseigné") { + 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); + } + if ( + data.errors["0"].message == "L'étudiant n'est pas inscrit au module" + ) { + const HTML = ` +

Attention, l'étudiant n'est pas inscrit à ce module.

+

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); + } + if ( + data.errors["0"].message == + "Duplication: la période rentre en conflit avec une plage enregistrée" + ) { + const HTML = ` +

L'assiduité n'a pas pu être enregistrée car une autre assiduité existe sur la période sélectionnée

+

Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.

+ `; + + const content = document.createElement("div"); + content.innerHTML = HTML; + + openAlertModal("Période conflictuelle", content); + } + with_errors = true; + } + }, + (data, status) => { + //error + console.error(data, status); + errorAlert(); + with_errors = true; + } + ); + return !with_errors; +} /** * Suppression d'une assiduité diff --git a/app/templates/assiduites/pages/ajout_assiduites.j2 b/app/templates/assiduites/pages/ajout_assiduites.j2 new file mode 100644 index 000000000..311b24a55 --- /dev/null +++ b/app/templates/assiduites/pages/ajout_assiduites.j2 @@ -0,0 +1,234 @@ +{% include "assiduites/widgets/toast.j2" %} +{% block pageContent %} +
+

Ajouter une assiduité

+ {% include "assiduites/widgets/tableau_base.j2" %} + {% if saisie_eval %} +
+
+

+ La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation.
+ Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation +

+ retourner sur la page de l'évaluation +
+ {% endif %} +
+
+
+
+ Date de début + + Journée entière +
+
+ Date de fin + +
+
+ +
+
+ Etat de l'assiduité + +
+
+
+
+ Module + {% with moduleid="ajout_assiduite_module_impl",label=false %} + {% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %} + {% endwith %} +
+
+ +
+
+ Raison + +
+
+ +
+ + +
+ + +
+ +
+
+ + {% include "assiduites/widgets/tableau_assi.j2" %} +
+ +
+ + + +{% endblock pageContent %} \ No newline at end of file diff --git a/app/templates/assiduites/pages/ajout_justificatif.j2 b/app/templates/assiduites/pages/ajout_justificatif.j2 index fac1b11ed..d74737146 100644 --- a/app/templates/assiduites/pages/ajout_justificatif.j2 +++ b/app/templates/assiduites/pages/ajout_justificatif.j2 @@ -3,10 +3,7 @@

Justifier des absences ou retards

{% include "assiduites/widgets/tableau_base.j2" %} -
- - {% include "assiduites/widgets/tableau_justi.j2" %} -
+
@@ -60,6 +57,10 @@
+
+ + {% include "assiduites/widgets/tableau_justi.j2" %} +
@@ -224,12 +225,12 @@ if (document.getElementById('justi_journee').checked) { date_deb.setAttribute("show", "date") date_fin.setAttribute("show", "date") - document.getElementById("date_fin").classList.add("hidden"); + document.querySelector(`legend[for="justi_date_fin"]`).removeAttribute("required") + } else { date_deb.removeAttribute("show") date_fin.removeAttribute("show") - document.getElementById("date_fin").classList.remove("hidden"); - + document.querySelector(`legend[for="justi_date_fin"]`).setAttribute("required", "") } } @@ -238,8 +239,12 @@ const date_fin = document.querySelector(".page #justi_date_fin") const journee = document.querySelector('.page #justi_journee').checked const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time) - const fin = (journee ? date_deb.valueAsObject.date : date_fin.valueAsObject.date) + "T" + (journee ? assi_evening : date_fin.valueAsObject.time) - + let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time) + if (journee) { + fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin + } else { + fin = date_fin.valueAsObject.date + fin + } return { "deb": deb, "fin": fin, diff --git a/app/templates/assiduites/pages/calendrier.j2 b/app/templates/assiduites/pages/calendrier.j2 index 0f9ef84bd..1114f628f 100644 --- a/app/templates/assiduites/pages/calendrier.j2 +++ b/app/templates/assiduites/pages/calendrier.j2 @@ -476,7 +476,7 @@ const matin = [new Date(date), new Date(date)] color = "sans_etat" matin[0].setHours(0, 0, 0, 0) - matin[1].setHours(12, 59, 59) + matin[1].setHours(12, 59, 59) // TODO Utiliser heure pivot (config) @@ -515,7 +515,7 @@ span_aprem.classList.add("color"); const aprem = [new Date(date), new Date(date)] color = "sans_etat" - aprem[0].setHours(13, 0, 0, 0) + aprem[0].setHours(13, 0, 0, 0) // TODO Utiliser heure pivot (config) aprem[1].setHours(23, 59, 59) diff --git a/app/templates/assiduites/widgets/moduleimpl_dynamic_selector.j2 b/app/templates/assiduites/widgets/moduleimpl_dynamic_selector.j2 index 84366dc62..cdb6c5585 100644 --- a/app/templates/assiduites/widgets/moduleimpl_dynamic_selector.j2 +++ b/app/templates/assiduites/widgets/moduleimpl_dynamic_selector.j2 @@ -1,13 +1,24 @@ -