diff --git a/app/models/groups.py b/app/models/groups.py index 8e8791ca0..72a54acf6 100644 --- a/app/models/groups.py +++ b/app/models/groups.py @@ -247,9 +247,9 @@ class GroupDescr(db.Model): d["partition"] = self.partition.to_dict(with_groups=False) return d - def get_edt_id(self) -> str: - "l'id pour l'emploi du temps: à défaut, le nom scodoc du groupe" - return self.edt_id or self.group_name or "" + def get_edt_ids(self) -> list[str]: + "les ids pour l'emploi du temps: à défaut, le nom scodoc du groupe" + return scu.split_id(self.edt_id) or [self.group_name] or [] def get_nb_inscrits(self) -> int: """Nombre inscrits à ce group et au formsemestre. diff --git a/app/scodoc/html_sco_header.py b/app/scodoc/html_sco_header.py index 7b8466977..1b9e45194 100644 --- a/app/scodoc/html_sco_header.py +++ b/app/scodoc/html_sco_header.py @@ -213,6 +213,7 @@ def sco_header( window.onload=function(){{enableTooltips("gtrcontent")}}; const SCO_URL="{scu.ScoURL()}"; + const SCO_TIMEZONE="{scu.TIME_ZONE}"; """ ) diff --git a/app/scodoc/sco_archives_justificatifs.py b/app/scodoc/sco_archives_justificatifs.py index 125569d01..60c3bd8db 100644 --- a/app/scodoc/sco_archives_justificatifs.py +++ b/app/scodoc/sco_archives_justificatifs.py @@ -157,10 +157,15 @@ class JustificatifArchiver(BaseArchiver): Si trace == True : sauvegarde le nom du/des fichier(s) supprimé(s) dans la trace de l'étudiant """ + print("debug : ", archive_name, filename, has_trace) if str(etud.id) not in self.list_oids(etud.dept_id): raise ValueError(f"Aucune archive pour etudid[{etud.id}]") - - archive_id = self.get_id_from_name(etud.id, archive_name, dept_id=etud.dept_id) + try: + archive_id = self.get_id_from_name( + etud.id, archive_name, dept_id=etud.dept_id + ) + except ScoValueError: + raise ValueError(f"Archive Inconnue [{archive_name}]") if filename is not None: if filename not in self.list_archive(archive_id, dept_id=etud.dept_id): diff --git a/app/scodoc/sco_edt_cal.py b/app/scodoc/sco_edt_cal.py index 7aebfce7f..eaa172a0f 100644 --- a/app/scodoc/sco_edt_cal.py +++ b/app/scodoc/sco_edt_cal.py @@ -55,6 +55,7 @@ def formsemestre_load_calendar( """Load ics data, return raw ics and decoded calendar. Raises ScoValueError if not configured or not available or invalid format. """ + edt_ids = [] if edt_id is None and formsemestre: edt_ids = formsemestre.get_edt_ids() if not edt_ids: @@ -408,6 +409,7 @@ def formsemestre_retreive_groups_from_edt_id( """Construit un dict donnant le groupe de chaque edt_id""" edt2group = {} for partition in formsemestre.partitions: - edt2group.update({g.get_edt_id(): g for g in partition.groups}) - edt2group.pop("", None) + for g in partition.groups: + for edt_id in g.get_edt_ids(): + edt2group[edt_id] = g return edt2group diff --git a/app/scodoc/sco_groups_edit.py b/app/scodoc/sco_groups_edit.py index f30d2ab2c..6c6cfd422 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.""", + les mêmes dans ScoDoc et dans l'emploi du temps (si plusieurs ids, + les séparer par des virgules).""", }, ), ), diff --git a/app/static/js/assiduites.js b/app/static/js/assiduites.js index fd4892b41..aadd04cae 100644 --- a/app/static/js/assiduites.js +++ b/app/static/js/assiduites.js @@ -305,8 +305,8 @@ function executeMassActionQueue() { */ const tlTimes = getTimeLineTimes(); let assiduite = { - date_debut: tlTimes.deb.toIsoUtcString(), - date_fin: tlTimes.fin.toIsoUtcString(), + date_debut: tlTimes.deb.toFakeIso(), + date_fin: tlTimes.fin.toFakeIso(), }; assiduite = setModuleImplId(assiduite); @@ -601,7 +601,10 @@ function toTime(time) { * @returns */ function formatDate(date, styles = { dateStyle: "full" }) { - return new Intl.DateTimeFormat("fr-FR", styles).format(date); + return new Intl.DateTimeFormat("fr-FR", { + ...{ timeZone: SCO_TIMEZONE }, + ...styles, + }).format(date); } /** @@ -610,19 +613,25 @@ function formatDate(date, styles = { dateStyle: "full" }) { function updateDate() { const dateInput = document.querySelector("#tl_date"); let date = $(dateInput).datepicker("getDate"); - if (date == null) { date = new Date(Date.fromFRA(dateInput.value)); } - + const intlOptions = { + dateStyle: "full", + timeZone: SCO_TIMEZONE, + }; let dateStr = ""; - if (!isNonWorkDay(date.getDay(), nonWorkDays)) { - dateStr = formatDate(date).capitalize(); + + if (!isNonWorkDay(date, nonWorkDays)) { + dateStr = formatDate(date, intlOptions).capitalize(); } else { // On se rend au dernier jour travaillé disponible const lastWorkDay = getNearestWorkDay(date); const att = document.createTextNode( - `Le jour sélectionné (${formatDate(date)}) n'est pas un jour travaillé.` + `Le jour sélectionné (${formatDate( + date, + intlOptions + )}) n'est pas un jour travaillé.` ); const div = document.createElement("div"); div.appendChild(att); @@ -630,22 +639,23 @@ function updateDate() { div.appendChild( document.createTextNode( `Le dernier jour travaillé disponible a été sélectionné : ${formatDate( - lastWorkDay + lastWorkDay, + intlOptions )}.` ) ); openAlertModal("Attention", div, "", "#eec660"); - const date_fra = Date.toFRA(lastWorkDay.toIsoUtcString().split("T")[0]); $(dateInput).datepicker("setDate", date_fra); dateInput.value = date_fra; date = lastWorkDay; - dateStr = formatDate(lastWorkDay).capitalize(); + dateStr = formatDate(lastWorkDay, { + dateStyle: "full", + timeZone: SCO_TIMEZONE, + }).capitalize(); } - console.warn(dateStr, date, date.toIsoUtcString()); - document.querySelector("#datestr").textContent = dateStr; return true; } @@ -654,7 +664,7 @@ function getNearestWorkDay(date) { const aDay = 86400000; // 24 * 3600 * 1000 | H * s * ms let day = date; let count = 0; - while (isNonWorkDay(day.getDay(), nonWorkDays) && count++ < 7) { + while (isNonWorkDay(day, nonWorkDays) && count++ < 7) { day = new Date(day - aDay); } return day; @@ -712,31 +722,12 @@ function formatDateModal(str, separator = " ") { * Renvoie Vrai si le jour est non travaillé */ function isNonWorkDay(day, nonWorkdays) { - let d = ""; - switch (day) { - case 0: - d = "dim"; - break; - case 1: - d = "lun"; - break; - case 2: - d = "mar"; - break; - case 3: - d = "mer"; - break; - case 4: - d = "jeu"; - break; - case 5: - d = "ven"; - break; - case 6: - d = "sam"; - break; - } - + const d = Intl.DateTimeFormat("fr-FR", { + timeZone: SCO_TIMEZONE, + weekday: "short", + }) + .format(day) + .replace(".", ""); return nonWorkdays.indexOf(d) != -1; } @@ -783,8 +774,8 @@ function getTimeLineTimes() { function isConflictSameAsPeriod(conflict, period = undefined) { const tlTimes = period == undefined ? getTimeLineTimes() : period; const clTimes = { - deb: new Date(conflict.date_debut), - fin: new Date(conflict.date_fin), + deb: new Date(Date.removeUTC(conflict.date_debut)), + fin: new Date(Date.removeUTC(conflict.date_fin)), }; return tlTimes.deb.isSame(clTimes.deb) && tlTimes.fin.isSame(clTimes.fin); } @@ -850,8 +841,8 @@ function numberTimeToDate(nb) { function getAssiduitesFromEtuds(clear, deb, fin) { const etudIds = Object.keys(etuds).join(","); - const date_debut = deb ? deb : getPrevDate().toIsoUtcString(); - const date_fin = fin ? fin : getNextDate().toIsoUtcString(); + const date_debut = deb ? deb : getPrevDate().toFakeIso(); + const date_fin = fin ? fin : getNextDate().toFakeIso(); if (clear) { assiduites = {}; @@ -894,8 +885,8 @@ function getAssiduitesFromEtuds(clear, deb, fin) { function createAssiduite(etat, etudid) { const tlTimes = getTimeLineTimes(); let assiduite = { - date_debut: tlTimes.deb.toIsoUtcString(), - date_fin: tlTimes.fin.toIsoUtcString(), + date_debut: tlTimes.deb.toFakeIso(), + date_fin: tlTimes.fin.toFakeIso(), etat: etat, }; @@ -937,6 +928,19 @@ function createAssiduite(etat, etudid) { 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); + } with_errors = true; } }, @@ -1076,8 +1080,8 @@ function getAssiduitesConflict(etudid, periode) { return etudAssiduites.filter((assi) => { const interval = { - deb: new Date(assi.date_debut), - fin: new Date(assi.date_fin), + deb: new Date(Date.removeUTC(assi.date_debut)), + fin: new Date(Date.removeUTC(assi.date_fin)), }; const test = hasTimeConflict(periode, interval); return test; @@ -1101,15 +1105,15 @@ function getLastAssiduiteOfPrevDate(etudid) { const prevAssiduites = etudAssiduites .filter((assi) => { const interval = { - deb: new Date(assi.date_debut), - fin: new Date(assi.date_fin), + deb: new Date(Date.removeUTC(assi.date_debut)), + fin: new Date(Date.removeUTC(assi.date_fin)), }; return hasTimeConflict(period, interval); }) .sort((a, b) => { - const a_fin = new Date(a.date_fin); - const b_fin = new Date(b.date_fin); + const a_fin = new Date(Date.removeUTC(a.date_fin)); + const b_fin = new Date(Date.removeUTC(b.date_fin)); return b_fin < a_fin; }); @@ -1144,8 +1148,8 @@ function getAssiduiteValue(field) { * @param {String | Number} etudid identifiant de l'étudiant */ function actualizeEtudAssiduite(etudid) { - const date_debut = getPrevDate().toIsoUtcString(); - const date_fin = getNextDate().toIsoUtcString(); + const date_debut = getPrevDate().toFakeIso(); + const date_fin = getNextDate().toFakeIso(); const url_api = getUrl() + @@ -1391,8 +1395,8 @@ function insertEtudRow(etud, index, output = false) { assiduite.etatAssiduite = conflict[0].etat; assiduite.id = conflict[0].assiduite_id; - assiduite.date_debut = conflict[0].date_debut; - assiduite.date_fin = conflict[0].date_fin; + assiduite.date_debut = Date.removeUTC(conflict[0].date_debut); + assiduite.date_fin = Date.removeUTC(conflict[0].date_fin); if (isConflictSameAsPeriod(conflict[0])) { assiduite.type = "édition"; } else { @@ -1623,9 +1627,7 @@ function getJustificatifFromPeriod(date, etudid, update) { getUrl() + `/api/justificatifs/${etudid}/query?date_debut=${date.deb .add(1, "seconds") - .toIsoUtcString()}&date_fin=${date.fin - .add(-1, "seconds") - .toIsoUtcString()}`, + .toFakeIso()}&date_fin=${date.fin.add(-1, "seconds").toFakeIso()}`, success: (data) => { update(data); }, @@ -1657,8 +1659,8 @@ function fastJustify(assiduite) { } const period = { - deb: new Date(assiduite.date_debut), - fin: new Date(assiduite.date_fin), + deb: new Date(Date.removeUTC(assiduite.date_debut)), + fin: new Date(Date.removeUTC(assiduite.date_fin)), }; const action = (justifs) => { //créer un nouveau justificatif @@ -1671,8 +1673,8 @@ function fastJustify(assiduite) { //créer justificatif const justif = { - date_debut: new Date(assiduite.date_debut).toIsoUtcString(), - date_fin: new Date(assiduite.date_fin).toIsoUtcString(), + date_debut: new Date(Date.removeUTC(assiduite.date_debut)).toFakeIso(), + date_fin: new Date(Date.removeUTC(assiduite.date_fin)).toFakeIso(), raison: raison, etat: etat, }; diff --git a/app/static/js/date_utils.js b/app/static/js/date_utils.js index ee9ed1503..05babeede 100644 --- a/app/static/js/date_utils.js +++ b/app/static/js/date_utils.js @@ -59,6 +59,11 @@ Date.intersect = function (period, interval) { return period.deb <= interval.fin && period.fin >= interval.deb; }; +Date.removeUTC = function (isoString) { + const reg = new RegExp(/[+-][\d:]+$/); + return isoString.replace(reg, ""); +}; + Object.defineProperty(Date.prototype, "isValid", { value: function () { return !Number.isNaN(this.getTime()); @@ -198,13 +203,29 @@ Object.defineProperty(Date.prototype, "toIsoUtcString", { * @returns date au format iso utc (yyyy-mm-ddThh:MM±oo:oo:oo) */ value: function () { + // Formater la date et l'heure const date = this; var tzo = -date.getTimezoneOffset(), dif = tzo >= 0 ? "+" : "-", pad = function (num) { return (num < 10 ? "0" : "") + num; }; + return ( + this.toFakeIso() + + dif + + pad(Math.floor(Math.abs(tzo) / 60)) + + ":" + + pad(Math.abs(tzo) % 60) + ); + }, +}); +Object.defineProperty(Date.prototype, "toFakeIso", { + value: function () { + const date = this; + pad = function (num) { + return (num < 10 ? "0" : "") + num; + }; return ( date.getFullYear() + "-" + @@ -216,11 +237,7 @@ Object.defineProperty(Date.prototype, "toIsoUtcString", { ":" + pad(date.getMinutes()) + ":" + - pad(date.getSeconds()) + - dif + - pad(Math.floor(Math.abs(tzo) / 60)) + - ":" + - pad(Math.abs(tzo) % 60) + pad(date.getSeconds()) ); }, }); @@ -246,6 +263,7 @@ Object.defineProperty(Date.prototype, "format", { hour: "2-digit", minute: "2-digit", hour12: false, + timeZone: SCO_TIMEZONE, }); case "DD/MM/YYYY HH:mm": return this.toLocaleString("fr-FR", { @@ -255,6 +273,7 @@ Object.defineProperty(Date.prototype, "format", { hour: "2-digit", minute: "2-digit", hour12: false, + timeZone: SCO_TIMEZONE, }); case "YYYY-MM-DDTHH:mm": @@ -264,7 +283,7 @@ Object.defineProperty(Date.prototype, "format", { case "YYYY-MM-DD": return iso.slice(0, iso.indexOf("T")); default: - return this.toIsoUtcString(); + return this.toFakeIso(); } }, }); diff --git a/app/templates/assiduites/pages/ajout_justificatif.j2 b/app/templates/assiduites/pages/ajout_justificatif.j2 index 445379ca4..fac1b11ed 100644 --- a/app/templates/assiduites/pages/ajout_justificatif.j2 +++ b/app/templates/assiduites/pages/ajout_justificatif.j2 @@ -103,7 +103,7 @@ [required]::after { content: "*"; - color: crimson; + color: var(--color-error); } -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/bul_head.j2 b/app/templates/bul_head.j2 index 91acfb3d9..8c725f012 100644 --- a/app/templates/bul_head.j2 +++ b/app/templates/bul_head.j2 @@ -18,8 +18,12 @@ Bulletin {{formsemestre.html_link_status() | safe}} - {% if formsemestre.etuds_inscriptions[etud.id].parcour %} - Parcours {{formsemestre.etuds_inscriptions[etud.id].parcour.code}} + {% if etud.id in formsemestre.etuds_inscriptions %} + {% if formsemestre.etuds_inscriptions[etud.id].parcour %} + Parcours {{formsemestre.etuds_inscriptions[etud.id].parcour.code}} + {% endif %} + {% else %} + {{scu.EMO_WARNING|safe}} non inscrit !? {% endif %} @@ -67,8 +71,8 @@ )}}">version courte spéciale BUT {% endif %} visualiser les compétences BUT diff --git a/sco_version.py b/sco_version.py index e8a195ffc..21b533cd9 100644 --- a/sco_version.py +++ b/sco_version.py @@ -1,7 +1,7 @@ # -*- mode: python -*- # -*- coding: utf-8 -*- -SCOVERSION = "9.6.58" +SCOVERSION = "9.6.60" SCONAME = "ScoDoc"