From 00efa7bcadb8776236c3ba709ce802e77ce343e7 Mon Sep 17 00:00:00 2001
From: Emmanuel Viennet <emmanuel.viennet@gmail.com>
Date: Tue, 24 Sep 2024 19:23:42 +0200
Subject: [PATCH] Fix signal_assiduites_group: saisie si timezone client !=
 serveur

---
 app/scodoc/sco_utils.py                        | 14 ++++++++++++++
 app/static/js/assiduites.js                    | 16 ++++++++--------
 .../assiduites/widgets/minitimeline.j2         |  4 ++--
 app/templates/assiduites/widgets/timeline.j2   | 18 +++++++++++-------
 sco_version.py                                 |  2 +-
 5 files changed, 36 insertions(+), 18 deletions(-)

diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index d4c710cc3..6b0e8fbc4 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -387,6 +387,20 @@ def localize_datetime(date: datetime.datetime) -> datetime.datetime:
     return new_date
 
 
+def get_local_timezone_offset() -> str:
+    """Récupère l'offset de la timezone du serveur, sous la forme
+    "+HH:MM"
+    """
+    local_time = datetime.datetime.now().astimezone()
+    utc_offset = local_time.utcoffset()
+    total_seconds = int(utc_offset.total_seconds())
+    offset_hours = total_seconds // 3600
+    offset_minutes = (abs(total_seconds) % 3600) // 60
+    offset_sign = "+" if offset_hours >= 0 else "-"
+    offset_str = f"{offset_sign}{abs(offset_hours):02d}:{offset_minutes:02d}"
+    return offset_str
+
+
 def is_period_overlapping(
     periode: tuple[datetime.datetime, datetime.datetime],
     interval: tuple[datetime.datetime, datetime.datetime],
diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js
index 446817247..ec4c1993a 100644
--- a/app/static/js/assiduites.js
+++ b/app/static/js/assiduites.js
@@ -223,27 +223,27 @@ function creerLigneEtudiant(etud, index) {
           </div>
       </div>
       <fieldset class="btns_field single" etudid="497" type="creation" assiduite_id="-1">
-        <input 
+        <input
           type="checkbox"
           value="present"
           name="btn_assiduites_1"
-          id="rbtn_present" 
+          id="rbtn_present"
           class="rbtn present"
           title="present"
         >
-        <input 
+        <input
           type="checkbox"
           value="retard"
           name="btn_assiduites_1"
-          id="rbtn_retard" 
+          id="rbtn_retard"
           class="rbtn retard"
           title="retard"
         >
-        <input 
+        <input
           type="checkbox"
           value="absent"
           name="btn_assiduites_1"
-          id="rbtn_absent" 
+          id="rbtn_absent"
           class="rbtn absent"
           title="absent"
         >
@@ -609,7 +609,7 @@ async function actionAssiduite(etud, etat, type, assiduite = null) {
   const modimpl_id = $("#moduleimpl_select").val();
   if (assiduite && assiduite.etat.toLowerCase() === etat) type = "suppression";
 
-  const { deb, fin } = getPeriodAsDate();
+  const { deb, fin } = getPeriodAsDate(true); // en tz server
   // génération d'un objet assiduité basique qui sera complété
   let assiduiteObjet = assiduite ?? {
     date_debut: deb,
@@ -709,7 +709,7 @@ function erreurModuleImpl(message) {
 function mettreToutLeMonde(etat, el = null) {
   const lignesEtuds = [...document.querySelectorAll("fieldset.btns_field")];
 
-  const { deb, fin } = getPeriodAsDate();
+  const { deb, fin } = getPeriodAsDate(true); // tz server
   const assiduiteObjet = {
     date_debut: deb,
     date_fin: fin,
diff --git a/app/templates/assiduites/widgets/minitimeline.j2 b/app/templates/assiduites/widgets/minitimeline.j2
index feab40fd7..1698ae0fe 100644
--- a/app/templates/assiduites/widgets/minitimeline.j2
+++ b/app/templates/assiduites/widgets/minitimeline.j2
@@ -59,7 +59,7 @@
             block.style.width = `${widthPercentage}%`;
 
             if (assiduité.etat != "CRENEAU") {
-                // Si on clique dessus on veut pouvoir 
+                // Si on clique dessus on veut pouvoir
                 // mettre à jour la timeline principale et modifier le moduleimpl_select
                 block.addEventListener("click", () => {
                     let deb = startDate.getHours() + startDate.getMinutes() / 60;
@@ -71,7 +71,7 @@
 
                     $("#moduleimpl_select").val(getModuleImplId(assiduité))
                     setTimeout(()=>{
-                        $("#moduleimpl_select").trigger("change"); 
+                        $("#moduleimpl_select").trigger("change");
                     }, 0)
                 });
                 //ajouter affichage assiduites on over
diff --git a/app/templates/assiduites/widgets/timeline.j2 b/app/templates/assiduites/widgets/timeline.j2
index e16045f04..ddc96ad52 100644
--- a/app/templates/assiduites/widgets/timeline.j2
+++ b/app/templates/assiduites/widgets/timeline.j2
@@ -12,6 +12,7 @@
     </div>
 </div>
 <script>
+    const SERVER_TIMEZONE_OFFSET = "{{ scu.get_local_timezone_offset() }}";
     const timelineContainer = document.querySelector(".timeline-container");
     const periodTimeLine = document.querySelector(".period");
     const t_start = {{ t_start }};
@@ -21,7 +22,7 @@
     const tick_time = 60 / {{ tick_time }};
     const tick_delay = 1 / tick_time;
 
-    const period_default = 2;
+    const period_default = 2; // durée créneau par défaut: 2 heures
 
     let handleMoving = false;
 
@@ -264,10 +265,10 @@
         // On les arrondit aux ticks les plus proches
         const startValue = snapToQuarter(startHour);
         const endValue = snapToQuarter(endHour);
-        
+
         // on verifie que les valeurs sont bien dans les bornes
         const computedValues = [Math.max(startValue, t_start), Math.min(t_end, endValue)];
-        
+
         // si les valeurs sont hors des bornes, on les ajuste
         if (computedValues[0] > t_end || computedValues[1] < t_start) {
             return [t_start, Math.min(t_end, t_start + period_default)];
@@ -338,19 +339,22 @@
     // Renvoie les valeurs de la période sous forme de date
     // Les heures sont récupérées depuis la timeline
     // la date est récupérée depuis un champ "#date" (datepicker)
-    function getPeriodAsDate(){
+    function getPeriodAsDate(add_server_tz = false) {
         let [deb, fin] = getPeriodValues();
         deb = numberToTime(deb);
-        fin = numberToTime(fin);    
+        fin = numberToTime(fin);
 
         const dateStr = $("#date")
         .datepicker("getDate")
         .format("yyyy-mm-dd")
         .substring(0, 10); // récupération que de la date, pas des heures
 
+        // Les heures deb et fin sont telles qu'affichées, c'est à dire
+        // en heure locale DU SERVEUR (des étudiants donc)
+        let offset = add_server_tz ? SERVER_TIMEZONE_OFFSET : "";
         return {
-            deb: new Date(`${dateStr}T${deb}`),
-            fin: new Date(`${dateStr}T${fin}`)
+            deb: new Date(`${dateStr}T${deb}${offset}`),
+            fin: new Date(`${dateStr}T${fin}${offset}`)
         }
     }
     // Sauvegarde les valeurs de la période dans le local storage
diff --git a/sco_version.py b/sco_version.py
index 0fc172aa9..4891ff4db 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
 # -*- mode: python -*-
 # -*- coding: utf-8 -*-
 
-SCOVERSION = "9.7.25"
+SCOVERSION = "9.7.26"
 
 SCONAME = "ScoDoc"